【问题标题】:recursive crawling with Python and Scrapy使用 Python 和 Scrapy 进行递归爬取
【发布时间】:2011-03-08 02:34:52
【问题描述】:

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

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

【问题讨论】:

  • 嗨。你的代码工作了吗?我应该像蜘蛛侠一样,将无法链接到另一个页面,但似乎无法找到annyt。你的工作代码可以是:使用。啧啧!
  • 不,我没有。我什至联系了scrapy的创建者,但他们没有帮助。
  • 我刚刚用不同的关键字进行了搜索,发现了这个:abuhijleh.net/2011/02/13/… 我希望它有所帮助。我还没有编写自己的爬虫。如果我做了什么,我会发布一些东西。
  • @Victor 字体不错。 +1 了

标签: python django scrapy


【解决方案1】:

抓取而不是蜘蛛?

因为您的原始问题需要重复导航一组连续且重复的内容,而不是未知大小的内容树,请使用 mechanize (http://www.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。将页面的代码驱动操作最小化为只显示您需要的数据将加快您的提取速度。

【讨论】:

    【解决方案2】:

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

    1. 通过在类下定义listing_url 列表。
    2. 通过在parse_listings() 中定义listing_url

    唯一的区别是措辞。另外,假设有五个页面要获取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】:

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

      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】:

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

        您使用 yield 有什么具体原因吗? 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;
        • 除了绝对和相对 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)
        

        【讨论】:

        • 因为如果我使用 return 它只会抓取一个列表并停止。它不会遍历每个列表并为其创建请求。你看,我在包含所有 15 个列表的页面上获得了一些信息,但是我必须去爬取该列表的单个页面以获取我需要的其余信息。 Yield 效果很好,直到我想添加抓取“下一页”的功能。
        • follow 看起来不像是 Request() 的参数,我收到错误 got an unexpected keyword argument 'follow'
        • 同样在这里得到got an unexpected keyword argument 'follow' 你是怎么解决的?
        • @SanjanaHE 我刚刚看了一下这段代码,它已经很老了。我已经更新了我的答案以反映最新版本的 Scrapy。我希望这会有所帮助。
        【解决方案5】:

        你可能想研究两件事。

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-08-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多