Сканирование URL-адресов сканирования в порядке

Итак, моя проблема относительно проста. У меня есть один паук, сканирующий несколько сайтов, и мне нужно, чтобы он возвращал данные в том порядке, в котором я записал их в своем коде. Это размещено ниже.

from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector
from mlbodds.items import MlboddsItem

class MLBoddsSpider(BaseSpider):
   name = "sbrforum.com"
   allowed_domains = ["sbrforum.com"]
   start_urls = [
       "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/",
       "http://www.sbrforum.com/mlb-baseball/odds-scores/20110329/",
       "http://www.sbrforum.com/mlb-baseball/odds-scores/20110330/"
   ]

   def parse(self, response):
       hxs = HtmlXPathSelector(response)
       sites = hxs.select('//div[@id="col_3"]//div[@id="module3_1"]//div[@id="moduleData4952"]')
       items = []
       for site in sites:
           item = MlboddsItem()
           item['header'] = site.select('//div[@class="scoreboard-bar"]//h2//span[position()>1]//text()').extract()# | /*//table[position()<2]//tr//th[@colspan="2"]//text()').extract()
           item['game1'] = site.select('/*//table[position()=1]//tr//td[@class="tbl-odds-c2"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c4"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c6"]//text()').extract()
           items.append(item)
       return items

Результаты возвращаются в случайном порядке, например, возвращается 29-е, затем 28-е, затем 30-е. Я попытался изменить порядок планировщика с DFO на BFO, на всякий случай, если это было проблемой, но это ничего не изменило.

Ответ 1

start_urls определяет URL-адреса, которые используются в start_requests. Ваш метод parse вызывается с ответом для каждого URL-адреса при загрузке страницы. Но вы не можете контролировать время загрузки - первый URL-адрес запуска может быть последним до parse.

Решение - переопределить метод start_requests и добавить к сгенерированным запросам a meta с ключом priority. В parse извлеките это значение priority и добавьте его в item. В трубопроводе сделайте что-то, основанное на этом значении. (Я не знаю, почему и где вам нужны эти URL-адреса для обработки в этом порядке).

Или сделайте это своего рода синхронным - сохраните эти стартовые URL-адреса. Вставьте start_urls первый из них. В parse обработать первый ответ и дать элемент (ы), затем взять следующий url из вашего хранилища и сделать запрос для него с обратным вызовом для parse.

Ответ 2

Scrapy Request теперь имеет атрибут priority.

Если у вас много Request в функции и вы хотите сначала обработать определенный запрос, вы можете установить:

def parse(self, response):
    url = 'http://www.example.com/first'
    yield Request(url=url, callback=self.parse_data, priority=1)

    url = 'http://www.example.com/second'
    yield Request(url=url, callback=self.parse_data)

Скрапия сначала обработает тот, который имеет priority=1.

Ответ 3

Обсуждение группы google предполагает использование атрибута priority в объекте Request. Scrapy гарантирует, что URL-адреса просканированы в DFO по умолчанию. Но это не гарантирует, что URL-адреса посещаются в том порядке, в котором они были получены в вашем обратном вызове parse.

Вместо того, чтобы давать объекты Request, вы хотите вернуть массив запросов, из которых будут выгружаться объекты до тех пор, пока они не будут пустыми.

Можете ли вы попробовать что-то подобное?

from scrapy.spider import BaseSpider
from scrapy.http import Request
from scrapy.selector import HtmlXPathSelector
from mlbodds.items import MlboddsItem

class MLBoddsSpider(BaseSpider):
   name = "sbrforum.com"
   allowed_domains = ["sbrforum.com"]

   def start_requests(self):
       start_urls = reversed( [
           "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/",
           "http://www.sbrforum.com/mlb-baseball/odds-scores/20110329/",
           "http://www.sbrforum.com/mlb-baseball/odds-scores/20110330/"
       ] )

       return [ Request(url = start_url) for start_url in start_urls ]

   def parse(self, response):
       hxs = HtmlXPathSelector(response)
       sites = hxs.select('//div[@id="col_3"]//div[@id="module3_1"]//div[@id="moduleData4952"]')
       items = []
       for site in sites:
           item = MlboddsItem()
           item['header'] = site.select('//div[@class="scoreboard-bar"]//h2//span[position()>1]//text()').extract()# | /*//table[position()<2]//tr//th[@colspan="2"]//text()').extract()
           item['game1'] = site.select('/*//table[position()=1]//tr//td[@class="tbl-odds-c2"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c4"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c6"]//text()').extract()
           items.append(item)
       return items

Ответ 4

Я сомневаюсь, что вы можете достичь того, чего хотите, если не играете с внутренними инструментами. Есть несколько подобных обсуждений по группам google scrapy, например.

http://groups.google.com/group/scrapy-users/browse_thread/thread/25da0a888ac19a9/1f72594b6db059f4?lnk=gst

Одна вещь, которая также может помочь настройка CONCURRENT_REQUESTS_PER_SPIDER до 1, но он не будет полностью обеспечивать порядок либо потому, что загрузчик имеет собственную локальную очередь по соображениям производительности, поэтому лучше вы можете сделать приоритет запросов но не обеспечить его точный порядок.

Ответ 5

Решение является последовательным.
Это решение похоже на @wuliang

Я начал с метода @Alexis de Tréglodé, но столкнулся с проблемой:
Тот факт, что ваш метод start_requests() возвращает список URL-адресов return [ Request(url = start_url) for start_url in start_urls ]
приводит к тому, что выход не является последовательным (асинхронным).

Если возврат - это один ответ, то путем создания альтернативы other_urls может выполнить требования. Кроме того, other_urls можно использовать для добавления URL-адресов, очищенных от других веб-страниц.

from scrapy import log
from scrapy.spider import BaseSpider
from scrapy.http import Request
from scrapy.selector import HtmlXPathSelector
from practice.items import MlboddsItem

log.start()

class PracticeSpider(BaseSpider):
    name = "sbrforum.com"
    allowed_domains = ["sbrforum.com"]

    other_urls = [
            "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/",
            "http://www.sbrforum.com/mlb-baseball/odds-scores/20110329/",
            "http://www.sbrforum.com/mlb-baseball/odds-scores/20110330/",
           ]

    def start_requests(self):
        log.msg('Starting Crawl!', level=log.INFO)
        start_urls = "http://www.sbrforum.com/mlb-baseball/odds-scores/20110327/"
        return [Request(start_urls, meta={'items': []})]

    def parse(self, response):
        log.msg("Begin Parsing", level=log.INFO)
        log.msg("Response from: %s" % response.url, level=log.INFO)
        hxs = HtmlXPathSelector(response)
        sites = hxs.select("//*[@id='moduleData8460']")
        items = response.meta['items']
        for site in sites:
            item = MlboddsItem()
            item['header'] = site.select('//div[@class="scoreboard-bar"]//h2//span[position()>1]//text()').extract()
            item['game1'] = site.select('/*//table[position()=1]//tr//td[@class="tbl-odds-c2"]//text()').extract()
            items.append(item)

        # here we .pop(0) the next URL in line
        if self.other_urls:
            return Request(self.other_urls.pop(0), meta={'items': items})

        return items

Ответ 6

Конечно, вы можете контролировать это. Главным секретом является метод подачи жадного Engine/Schedulor. Требование - это всего лишь немного. Посмотрите, что я добавляю список с именем "task_urls".

from scrapy.spider import BaseSpider
from scrapy.selector import HtmlXPathSelector
from scrapy.http.request import Request
from dirbot.items import Website

class DmozSpider(BaseSpider):
   name = "dmoz"
   allowed_domains = ["sbrforum.com"]
   start_urls = [
       "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/",
   ]
   task_urls = [
       "http://www.sbrforum.com/mlb-baseball/odds-scores/20110328/",
       "http://www.sbrforum.com/mlb-baseball/odds-scores/20110329/",
       "http://www.sbrforum.com/mlb-baseball/odds-scores/20110330/"
   ]
   def parse(self, response): 

       hxs = HtmlXPathSelector(response)
       sites = hxs.select('//div[@id="col_3"]//div[@id="module3_1"]//div[@id="moduleData4952"]')
       items = []
       for site in sites:
           item = Website()
           item['header'] = site.select('//div[@class="scoreboard-bar"]//h2//span[position()>1]//text()').extract()# | /*//table[position()<2]//tr//th[@colspan="2"]//text()').extract()
           item['game1'] = site.select('/*//table[position()=1]//tr//td[@class="tbl-odds-c2"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c4"]//text() | /*//table[position()=1]//tr//td[@class="tbl-odds-c6"]//text()').extract()
           items.append(item)
       # Here we feed add new request
       self.task_urls.remove(response.url)
       if self.task_urls:
           r = Request(url=self.task_urls[0], callback=self.parse)
           items.append(r)

       return items

Если вам нужен более сложный случай, см. мой проект: https://github.com/wuliang/TiebaPostGrabber

Ответ 7

Существует гораздо более простой способ сделать scrapy следовать порядку start_url: вы можете просто раскомментировать и изменить параллельные запросы в settings.py на 1.

Configure maximum concurrent requests performed by Scrapy (default: 16) 
CONCURRENT_REQUESTS = 1

Ответ 8

Отказ от ответственности: не работал с особой помощью

Скребок может быть в очереди и требовать запросы, основанные на тайм-аутах и ​​ошибках HTTP, было бы намного проще, если бы вы могли получить дату со страницы ответа?

т.е. добавьте еще один оператор hxs.select, который захватывает дату (просто посмотрел, это определенно в данных ответа) и добавьте это к элементу dict, сортируйте элементы на основе этого.

Это, вероятно, более надежный подход, а не полагаться на порядок царапин...

Ответ 9

Я считаю, что

hxs.select('...')

который вы сделаете, очистят данные с сайта в том порядке, в котором они отображаются. Либо это, либо scrapy проходит через ваш start_urls в произвольном порядке. Чтобы заставить его пройти через них в предопределенном порядке, и помните, что это не сработает, если вам нужно обходить больше сайтов, тогда вы можете попробовать следующее:

start_urls = ["url1.html"]

def parse1(self, response):
    hxs = HtmlXPathSelector(response)
   sites = hxs.select('blah')
   items = []
   for site in sites:
       item = MlboddsItem()
       item['header'] = site.select('blah')
       item['game1'] = site.select('blah')
       items.append(item)
   return items.append(Request('url2.html', callback=self.parse2))

затем напишите parse2, который делает то же самое, но добавляет Request для url3.html с обратным вызовом = self.parse3. Это ужасный стиль кодирования, но я просто выбрасываю его, если вам нужен быстрый хак.

Ответ 10

Лично мне нравится реализация @user1460015 после того, как мне удалось самостоятельно решить проблему.

Мое решение состоит в том, чтобы использовать подпроцесс Python, чтобы вызвать URL-адрес scrapy по URL-адресу, пока все URL-адреса не позаботились.

В моем коде, если пользователь не указывает, что он/она хочет последовательно анализировать URL-адреса, мы можем запустить паук обычным способом.

process = CrawlerProcess({'USER_AGENT': 'Mozilla/4.0 (compatible; \
    MSIE 7.0; Windows NT 5.1)'})
process.crawl(Spider, url = args.url)
process.start()

Если пользователь указывает, что это необходимо сделать последовательно, мы можем сделать это:

for url in urls:
    process = subprocess.Popen('scrapy runspider scrapper.py -a url='\
        + url + ' -o ' + outputfile)
    process.wait()

Обратите внимание: эта реализация не обрабатывает ошибки.