Подтвердить что ты не робот

Рекурсивное сканирование с помощью Python и Scrapy

Я использую scrapy для сканирования сайта. На сайте есть 15 записей на странице, а затем есть следующая кнопка. Я столкнулся с проблемой, когда мой запрос для следующей ссылки вызывается до того, как я закончу разбор всех моих списков в конвейере. Вот код для моего паука:

class MySpider(CrawlSpider):
    name = 'mysite.com'
    allowed_domains = ['mysite.com']
    start_url = 'http://www.mysite.com/'

    def start_requests(self):
        return [Request(self.start_url, callback=self.parse_listings)]

    def parse_listings(self, response):
        hxs = HtmlXPathSelector(response)
        listings = hxs.select('...')

        for listing in listings:
            il = MySiteLoader(selector=listing)
            il.add_xpath('Title', '...')
            il.add_xpath('Link', '...')

            item = il.load_item()
            listing_url = listing.select('...').extract()

            if listing_url:
                yield Request(urlparse.urljoin(response.url, listing_url[0]),
                              meta={'item': item},
                              callback=self.parse_listing_details)

        next_page_url = hxs.select('descendant::div[@id="pagination"]/'
                                   'div[@class="next-link"]/a/@href').extract()
        if next_page_url:
            yield Request(urlparse.urljoin(response.url, next_page_url[0]),
                          callback=self.parse_listings)


    def parse_listing_details(self, response):
        hxs = HtmlXPathSelector(response)
        item = response.request.meta['item']
        details = hxs.select('...')
        il = MySiteLoader(selector=details, item=item)

        il.add_xpath('Posted_on_Date', '...')
        il.add_xpath('Description', '...')
        return il.load_item()

Эти строки являются проблемой. Как я уже сказал, они исполняются до того, как паук закончил сканирование текущей страницы. На каждой странице сайта это всего лишь 3 из 15 моих списков, которые будут отправлены в конвейер.

     if next_page_url:
            yield Request(urlparse.urljoin(response.url, next_page_url[0]),
                          callback=self.parse_listings)

Это мой первый паук и может быть ошибкой дизайна с моей стороны, есть ли лучший способ сделать это?

4b9b3361

Ответ 1

Скопировать вместо паука?

Поскольку ваша оригинальная проблема требует повторной навигации по последовательному и повторяемому набору контента вместо дерева содержимого неизвестного размера, используйте mechanize (http://wwwsearch.sourceforge.net/mechanize/) и beautifulsoup (http://www.crummy.com/software/BeautifulSoup/).

Вот пример создания экземпляра браузера с помощью механизации. Кроме того, использование br.follow_link (text = "foo" ) означает, что в отличие от xpath в вашем примере ссылки будут по-прежнему выполняться независимо от структуры элементов в пути предка. Смысл, если они обновят свой HTML, ваши разрывы script. Свободная муфта сохранит вам некоторое обслуживание. Вот пример:

br = mechanize.Browser()
br.set_handle_equiv(True)
br.set_handle_redirect(True)
br.set_handle_referer(True)
br.set_handle_robots(False)
br.addheaders = [('User-agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:9.0.1)Gecko/20100101 Firefox/9.0.1')]
br.addheaders = [('Accept-Language','en-US')]
br.addheaders = [('Accept-Encoding','gzip, deflate')]
cj = cookielib.LWPCookieJar()
br.set_cookiejar(cj)
br.open("http://amazon.com")
br.follow_link(text="Today Deals")
print br.response().read()

Кроме того, в "следующем 15" href есть, вероятно, что-то, указывающее разбиение на страницы, например. & Амп; индекс = 15. Если общее количество элементов на всех страницах доступно на первой странице, то:

soup = BeautifulSoup(br.response().read())
totalItems = soup.findAll(id="results-count-total")[0].text
startVar =  [x for x in range(int(totalItems)) if x % 15 == 0]

Затем просто перетащите поверх startVar и создайте url, добавьте значение startVar в url, br.open() и очистите данные. Таким образом, вам не нужно программно "найти" "следующую" ссылку на странице и выполнить щелчок по ней, чтобы перейти на следующую страницу - вы уже знаете все допустимые URL-адреса. Минимизация управляемой кодами манипуляции страницей только с необходимыми данными ускорит извлечение.

Ответ 2

Существует два способа сделать это последовательно:

  • определяя список listing_url в классе.
  • определяя listing_url внутри parse_listings().

Единственное различие - это слово. Кроме того, предположим, что есть пять страниц, чтобы получить listing_urls. Поэтому поставьте page=1 под класс.

В методе parse_listings сделайте запрос один раз. Поместите все данные в meta, которые вам нужно отслеживать. При этом используйте parse_listings только для анализа "главной страницы".

Как только вы достигнете конца строки, верните свои предметы. Этот процесс является последовательным.

class MySpider(CrawlSpider):
    name = 'mysite.com'
    allowed_domains = ['mysite.com']
    start_url = 'http://www.mysite.com/'

    listing_url = []
    page = 1

    def start_requests(self):
        return [Request(self.start_url, meta={'page': page}, callback=self.parse_listings)]

    def parse_listings(self, response):
        hxs = HtmlXPathSelector(response)
        listings = hxs.select('...')

        for listing in listings:
            il = MySiteLoader(selector=listing)
            il.add_xpath('Title', '...')
            il.add_xpath('Link', '...')

        items = il.load_item()

        # populate the listing_url with the scraped URLs
        self.listing_url.extend(listing.select('...').extract())

        next_page_url = hxs.select('descendant::div[@id="pagination"]/'
                                   'div[@class="next-link"]/a/@href').extract()

        # now that the front page is done, move on to the next listing_url.pop(0)
        # add the next_page_url to the meta data
        return Request(urlparse.urljoin(response.url, self.listing_url.pop(0)),
                            meta={'page': self.page, 'items': items, 'next_page_url': next_page_url},
                            callback=self.parse_listing_details)

    def parse_listing_details(self, response):
        hxs = HtmlXPathSelector(response)
        item = response.request.meta['item']
        details = hxs.select('...')
        il = MySiteLoader(selector=details, item=item)

        il.add_xpath('Posted_on_Date', '...')
        il.add_xpath('Description', '...')
        items = il.load_item()

        # check to see if you have any more listing_urls to parse and last page
        if self.listing_urls:
            return Request(urlparse.urljoin(response.url, self.listing_urls.pop(0)),
                            meta={'page': self.page, 'items': items, 'next_page_url': response.meta['next_page_url']},
                            callback=self.parse_listings_details)
        elif not self.listing_urls and response.meta['page'] != 5:
            # loop back for more URLs to crawl
            return Request(urlparse.urljoin(response.url, response.meta['next_page_url']),
                            meta={'page': self.page + 1, 'items': items},
                            callback=self.parse_listings)
        else:
            # reached the end of the pages to crawl, return data
            return il.load_item()

Ответ 3

См. ниже обновленный ответ в разделе EDIT 2 (обновлено 6 октября 2017 года)

Есть ли какая-то конкретная причина, по которой вы используете доход? Выход возвращает генератор, который будет возвращать объект Request, когда на него вызывается .next().

Измените свои операторы yield на выражения return, и все должно работать должным образом.

Вот пример генератора:

In [1]: def foo(request):
   ...:     yield 1
   ...:     
   ...:     

In [2]: print foo(None)
<generator object foo at 0x10151c960>

In [3]: foo(None).next()
Out[3]: 1

EDIT:

Измените функцию def start_requests(self), чтобы использовать параметр follow.

return [Request(self.start_url, callback=self.parse_listings, follow=True)]

ИЗМЕНИТЬ 2:

Начиная с версии Scope v1.4.0, выпущенной в 2017-05-18, теперь рекомендуется использовать response.follow вместо непосредственного создания объектов scrapy.Request.

Из примечания к выпуску:

Создает новый метод response.follow для создания запросов; сейчас рекомендуемый способ создания запросов в пауках Scrapy. Этот метод облегчает запись правильных пауков; response.follow имеет несколько преимущества над созданием объектов scrapy.Request напрямую:

  • обрабатывает относительные URL-адреса;
  • он работает правильно с URL-адресами, отличными от ascii, на страницах, отличных от UTF8;
  • в дополнение к абсолютным и относительным URL-адресам, он поддерживает Selectors; для элементов он также может извлекать свои значения href.

Итак, для ОП выше, измените код на:

    next_page_url = hxs.select('descendant::div[@id="pagination"]/'
                               'div[@class="next-link"]/a/@href').extract()
    if next_page_url:
        yield Request(urlparse.urljoin(response.url, next_page_url[0]),
                      callback=self.parse_listings)

в

    next_page_url = hxs.select('descendant::div[@id="pagination"]/'
                               'div[@class="next-link"]/a/@href')
    if next_page_url is not None:
        yield response.follow(next_page_url, self.parse_listings)

Ответ 4

Вы можете получать запросы или элементы столько раз, сколько вам нужно.

def parse_category(self, response):
    # Get links to other categories
    categories = hxs.select('.../@href').extract()

    # First, return CategoryItem
    yield l.load_item()

    for url in categories:
        # Than return request for parse category
        yield Request(url, self.parse_category)

Я обнаружил, что здесь - https://groups.google.com/d/msg/scrapy-users/tHAAgnuIPR4/0ImtdyIoZKYJ

Ответ 5

Возможно, вы захотите изучить две вещи.

  • Веб-сайт, который вы просматриваете, может блокировать определяемый вами пользовательский агент.
  • Попробуйте добавить DOWNLOAD_DELAY к вашему пауку.

Ответ 6

Я просто исправил эту же проблему в своем коде. Я использовал базу данных SQLite3, которая входит в состав Python 2.7, чтобы исправить ее: каждый элемент, который вы собираете информацию, получает свою уникальную строку, помещенную в таблицу базы данных в первом проходе функции синтаксического анализа, и каждый экземпляр обратного вызова синтаксического анализа добавляет каждый данные позиции в таблицу и строку для этого элемента. Храните счетчик экземпляров, чтобы последняя процедура разбора обратного вызова знала, что она последняя, ​​и записывает CSV файл из базы данных или что-то еще. Обратный вызов может быть рекурсивным, когда ему сообщается в мета, где схема синтаксического анализа (и, конечно, какой элемент) была отправлена ​​для работы. Работает для меня как шарм. У вас есть SQLite3, если у вас есть Python. Здесь был мой пост, когда я впервые обнаружил ограничение скрининга в этом отношении: Является ли асинхронность Scrapy тем, что затрудняет создание моего файла результатов CSV?