使用Python和Scrapy进行递归爬行

时间:2011-03-08 02:34:53

标签: python django 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个被发送到管道。

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

这是我的第一个蜘蛛,可能是我的设计缺陷,有更好的方法吗?

7 个答案:

答案 0 :(得分: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中,可能存在表示分页的东西,例如&安培;指数= 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。将代码驱动的页面操作最小化为仅需要的数据将加速提取。

答案 1 :(得分:3)

有两种方法可以按顺序执行此操作:

  1. 在类下定义listing_url列表。
  2. 通过在listing_url内定义parse_listings()
  3. 唯一的区别是verbage。另外,假设有五页要获得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()
    

答案 2 :(得分:1)

在编辑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个对象。

来自release notes

  

有一个新的response.follow方法用于创建请求;就是现在   在Scrapy蜘蛛中创建请求的推荐方法。这种方法   可以更容易地编写正确的蜘蛛; response.follow有几个   优于直接创建scrapy.Request对象的优势:

     
      
  • 它处理相对的URL;
  •   
  • 适用于非UTF8页面上的非ascii网址;
  •   
  • 除了支持选择器的绝对和相对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)

答案 3 :(得分: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

答案 4 :(得分:0)

您可能希望了解两件事。

  1. 您抓取的网站可能阻止您定义的用户代理。
  2. 尝试向您的蜘蛛添加DOWNLOAD_DELAY。

答案 5 :(得分:0)

我刚刚在我的代码中解决了同样的问题。我使用SQLite3数据库作为Python 2.7的一部分来修复它:你收集信息的每个项目在解析函数的第一次传递中将其唯一的行放入数据库表中,并且解析回调的每个实例都添加每个item的数据到该项的表和行。保留一个实例计数器,以便最后一个回调解析例程知道它是最后一个,并从数据库或其他任何地方写入CSV文件。回调可以是递归的,在meta中被告知解析它被调度使用的模式(当然还有哪个项目)。对我来说就像一个魅力。如果你有Python,你有SQLite3。当我第一次发现scrapy在这方面的局限性时,这是我的帖子: Is Scrapy's asynchronicity what is hindering my CSV results file from being created straightforwardly?

答案 6 :(得分:0)

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

此示例显示如何使用不同的技术从网站抓取多个下一页