12

我正在使用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()

这些行是问题所在。就像我之前说的,它们在蜘蛛完成对当前页面的爬取之前被执行。在网站的每个页面上,这导致我的 15 个列表中只有 3 个被发送到管道。

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

这是我的第一个蜘蛛,可能是我的设计缺陷,有没有更好的方法来做到这一点?

4

7 回答 7

4

刮而不是蜘蛛?

因为您的原始问题需要重复导航一组连续且重复的内容,而不是未知大小的内容树,请使用 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”href 中可能有一些指示分页的内容,例如 &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。将页面的代码驱动操作最小化为您需要的数据将加快您的提取速度。

于 2012-02-28T16:48:50.810 回答
3

有两种顺序执行此操作的方法:

  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()
于 2012-06-29T00:30:23.490 回答
1

您可以根据需要多次生成请求或项目。

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

于 2012-08-03T10:22:20.060 回答
1

请参阅下面的 EDIT 2 部分下的更新答案(2017 年 10 月 6 日更新)

您使用产量有什么具体原因吗?Yield 将返回一个生成器,该生成器将在.next()调用它时返回 Request 对象。

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

编辑:

更改您的def start_requests(self)函数以使用该follow参数。

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

编辑2:

从 2017-05-18 发布的 Scrapy v1.4.0 开始,现在建议使用response.follow而不是直接创建scrapy.Request对象。

发行说明

有一个新的 response.follow 方法用于创建请求;它现在是在 Scrapy 蜘蛛中创建请求的推荐方法。这种方法使编写正确的蜘蛛更容易;与直接创建 scrapy.Request 对象相比,response.follow 有几个优点:

  • 它处理相对 URL;
  • 它适用于非 UTF8 页面上的非 ascii URL;
  • 除了绝对和相对 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)
于 2011-03-08T03:43:12.433 回答
0

你可能想研究两件事。

  1. 您正在抓取的网站可能会阻止您定义的用户代理。
  2. 尝试将 DOWNLOAD_DELAY 添加到您的蜘蛛。
于 2011-12-05T05:07:59.427 回答
0

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

这个例子展示了如何使用不同的技术从一个网站中删除多个下一页

于 2014-04-10T14:53:23.953 回答
0

我刚刚在我的代码中解决了同样的问题。我使用了作为 Python 2.7 一部分的 SQLite3 数据库来修复它:您收集的每个项目的信息都会在解析函数的第一遍中将其唯一的行放入数据库表中,并且解析回调的每个实例都会添加每个项目的数据到该项目的表和行。保留一个实例计数器,以便最后一个回调解析例程知道它是最后一个,并从数据库或其他任何地方写入 CSV 文件。回调可以是递归的,在元中被告知它被分派使用哪个解析模式(当然还有哪个项目)。像魅力一样为我工作。如果你有 Python,你就有 SQLite3。这是我第一次发现scrapy在这方面的局限性时的帖子: Scrapy 的异步性是什么阻碍了我的 CSV 结果文件的直接创建?

于 2014-01-30T08:12:49.047 回答