рекурсивное сканирование с Python и Scrapy - PullRequest
12 голосов
/ 08 марта 2011

Я использую скрапс для сканирования сайта. Сайт имеет 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)

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

Ответы [ 7 ]

4 голосов
/ 28 февраля 2012

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

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

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

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's Deals")
print br.response().read()

Кроме того, в «следующих 15» разделах, вероятно, есть что-то, указывающее на нумерацию страниц, например & index= 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-адреса.Минимизация манипулирования страницей с помощью кода только теми данными, которые вам нужны, ускорит извлечение.

3 голосов
/ 29 июня 2012

Есть два способа сделать это последовательно:

  1. путем определения списка listing_url в классе.
  2. , определив 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()
1 голос
/ 03 августа 2012

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

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

1 голос
/ 08 марта 2011

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

Есть ли какая-то конкретная причина, по которой вы используете yield? Yield вернет генератор, который вернет объект 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:

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

Из заметок о выпуске :

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

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

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

    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)
0 голосов
/ 10 апреля 2014

http://autopython.blogspot.com/2014/04/recursive-scraping-using-different.html

в этом примере показано, как извлечь несколько следующих страниц с веб-сайта, используя различные методы

0 голосов
/ 30 января 2014

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

0 голосов
/ 05 декабря 2011

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

  1. Сканируемый вами веб-сайт может блокировать определенный вами пользовательский агент.
  2. Попробуйте добавить DOWNLOAD_DELAY к вашему пауку.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...