2011-03-08 89 views
12

我正在使用scrapy來爬網。該網站每頁有15個列表,然後有一個下一個按鈕。我遇到了一個問題,在我完成解析管道中的所有列表之前,我正在調用請求下一個鏈接的請求。這裏是我的蜘蛛代碼:使用Python和Scrapy進行遞歸爬行

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) 

這是我的第一隻蜘蛛,可能是我的設計缺陷,有沒有更好的方法來做到這一點?

+1

喜放棄從網站多張下一頁。你有代碼工作嗎?我希望蜘蛛爬到下一頁,但似乎無法找到任何教程。你的工作代碼可能很有用。謝謝! – Victor 2011-03-31 14:15:59

+0

不,我沒有。我甚至聯繫過scrapy的創作者,但他們沒有幫助。 – imns 2011-03-31 15:02:42

+0

我只是用不同的關鍵字搜索,發現這個:http://abuhijleh.net/2011/02/13/guide-scrape-multi-pages-content-with-scrapy/我希望它有幫助。我還沒有編寫我自己的抓取工具。如果我做了什麼,我會發布一些東西。 – Victor 2011-04-01 03:23:35

回答

1

請參閱以下更新的答案,下面的編輯2段(更新2017年10月6日)

是否有您所使用的任何產量具體的原因?收益率將返回一個生成器,當它調用.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:

由於Scrapy V1.4.0,在2017年5月18日發佈的,現在推薦使用response.follow,而不是直接創造scrapy.Request對象。

release notes

有創建請求新response.follow方法;現在是 推薦的方式在Scrapy蜘蛛中創建請求。這種方法 可以更容易地編寫正確的蜘蛛; response.follow有幾個優勢直接創建scrapy.Request對象:

  • 它處理相對URL;
  • 它適用於非UTF8頁面上的非ASCII網址;
  • 除了支持Selectors的絕對和相對URL之外,對於元素,它也可以提取它們的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) 
    

    到:

因此,對於上面的OP,該代碼更改

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) 
+1

+1,因爲如果我使用退貨,它只會抓取一個列表並停止。它不會遍歷每個列表併爲其創建請求。您會看到,我在包含全部15個列表的網頁上獲取了一些關於我的信息,但隨後我必須抓取該列表的單個頁面以獲取我需要的其餘信息。良率很好,直到我想添加抓取「下一頁」的功能。 – imns 2011-03-09 00:41:24

+0

啊,好的。那麼我會更新我的答案。 – 2011-03-09 01:26:34

+2

下面看起來不像是Request()的一個參數,我得到一個錯誤'有一個意外的關鍵字參數'follow'' – imns 2011-03-09 02:38:13

0

你可能要考慮兩件事情。

  1. 您正在抓取的網站可能會阻止您定義的用戶代理。
  2. 嘗試添加一個DOWNLOAD_DELAY到你的蜘蛛。
4

刮而不是蜘蛛?

因爲您的原始問題需要重複導航連續和重複的一組內容而不是未知大小的內容樹,請使用機械化(http://wwwsearch.sourceforge.net/mechanize/)和beautifulsoup(http ://www.crummy.com/software/BeautifulSoup/)。

下面是使用機械化實例化瀏覽器的示例。另外,使用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並創建網址,startVar的值添加到URL,br.open()它並刮掉數據。這樣,您就不必通過編程的方式「查找」頁面上的「下一個」鏈接並點擊它來進入下一頁 - 您已經知道所有有效的網址。將代碼驅動的頁面操作最小化爲僅需要的數據將加速提取。

+0

這是題外話,他已經在使用Scrapy。 – 2013-08-11 07:58:19

3

這樣做有順序的方法有兩種:

  1. 由類下定義listing_url列表。
  2. 通過在parse_listings()內定義listing_url

唯一的區別是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() 
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

0

我只是定格在我的代碼同樣的問題。我使用了作爲Python 2.7的一部分的SQLite3數據庫來修復它:每個收集信息的項目在解析函數的第一遍中將其唯一行放入數據庫表中,並且解析回調的每個實例都添加項目的數據到該項目的表格和行。保留一個實例計數器,以便最後的回調解析例程知道它是最後一個,並從數據庫或其他任何地方寫入CSV文件。回調可以是遞歸的,在meta中被告知哪些解析模式(當然是哪個項目)被分派使用。對我來說就像一個魅力。如果你有Python,你有SQLite3。這裏是我的帖子的時候我第一次發現scrapy的限制,在這方面: Is Scrapy's asynchronicity what is hindering my CSV results file from being created straightforwardly?