日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Scrapy定向爬虫教程(三)——爬取多个页面

發(fā)布時間:2024/1/23 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Scrapy定向爬虫教程(三)——爬取多个页面 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本節(jié)內(nèi)容

本部分所實現(xiàn)的功能是,批量的爬取網(wǎng)頁信息,不再是像以前那樣只能下載一個頁面了。也就是說,分析出網(wǎng)頁的url規(guī)律后,用特定的算法去迭代,達到把整個網(wǎng)站的有效信息都拿下的目的。
因為本部分講完后,功能已經(jīng)到了可以使用的地步,所以我把本部分的結(jié)果獨立出來,把項目上傳到了github,小伙伴可以下載參考,地址https://github.com/kongtianyi/heartsong。教程余下的其他部分是添加功能和優(yōu)化,今后我會在github上創(chuàng)建擁有不同擴展功能的分支。

分析url

不管是Discuz模板,phpWind模板,還是百度貼吧,甚至某些新聞網(wǎng),都是采用id的方式來組織網(wǎng)頁url的。這就給我們編寫定向爬蟲帶來了極大的便利。好,來看一下Discuz模板心韻論壇的url:

http://www.heartsong.top/forum.php?mod=viewthread&tid=13&extra=page%3D1 http://www.heartsong.top/forum.php?mod=viewthread&tid=31 http://www.heartsong.top/forum.php?mod=viewthread&tid=31&extra=&page=2

共同點一目了然,其實我們不妨把參數(shù)改一改,空的參數(shù)去掉,下面三個url跟上面的三個請求到的頁面是一樣的

http://www.heartsong.top/forum.php?mod=viewthread&tid=13 http://www.heartsong.top/forum.php?mod=viewthread&tid=31 http://www.heartsong.top/forum.php?mod=viewthread&tid=31&page=2

局勢更清晰了,所謂的tid,就是帖子的id,而參數(shù)page,就是若主題帖分頁的話,主題帖的某一頁,當然第一頁也可以加上page參數(shù),http://www.heartsong.top/forum.PHP?mod=viewthread&tid=13&page=1,一樣可以請求到網(wǎng)頁。
大部分的網(wǎng)站首頁上都會有“最新帖子”,“最新新聞”這種模塊,點進去就能找到tid的上限,若是沒有的話,那就乖乖多次嘗試吧,下限一般都是從零開始,不必多說。而page參數(shù),需要我們在主題帖的第一頁通過網(wǎng)頁元素的分析去尋找出來。

根據(jù)我的經(jīng)驗,在很多論壇里,包括我的這個小破論壇,都或多或少的遭到廣告的侵襲,會有很多tid對應的帖子被管理員刪掉,所以下面的代碼里我們要對這種帖子做相應的處理。一般來說,Discuz被刪帖的tid或者是還沒排到的tid會返回如下頁面

爬取思路

通過以上的分析,我們可以得出這樣的思路:
1 通過某種機制去迭代tid
2 在主題帖第一頁中分析出總頁數(shù),去迭代帶page參數(shù)的url

一些雜項

對于某些網(wǎng)站,他們有識別爬蟲的機制,所以我們需要對我們的爬蟲進行一定的偽裝,在heartsong_spider.py中加入以下項。
其中,cookies在后面教程的回帖部分會用到,此處可以先置空。

# 用來保持登錄狀態(tài),可把chrome上拷貝下來的字符串形式cookie轉(zhuǎn)化成字典形式,粘貼到此處 cookies = {}# 發(fā)送給服務(wù)器的http頭信息,有的網(wǎng)站需要偽裝出瀏覽器頭進行爬取,有的則不需要 headers = {# 'Connection': 'keep - alive','User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36' }# 對請求的返回進行處理的配置 meta = {'dont_redirect': True, # 禁止網(wǎng)頁重定向'handle_httpstatus_list': [301, 302] # 對哪些異常返回進行處理 }
  • 1

重載start_requests

在找到tid的上限后,要想帶著上面配置的雜項去發(fā)起Request請求,我們需要重載一個函數(shù),配置使用star_urls所發(fā)起的第一條請求。
這里對heartsong_spider.py中的yield做一下解釋,它既可以傳出一個item到pipeline進行加工,也可以傳出一個新的Request請求。在傳出一個新請求的時候,就會多開啟一個線程,Scrapy是異步多線程的爬蟲框架,不需要我們對多線程有過多的了解。

def start_requests(self):"""這是一個重載函數(shù),它的作用是發(fā)出第一個Request請求:return:"""# 帶著headers、cookies去請求self.start_urls[0],返回的response會被送到# 回調(diào)函數(shù)parse中yield Request(self.start_urls[0],callback=self.parse, headers=self.headers,cookies=self.cookies, meta=self.meta)
  • 1

編寫迭代tid的函數(shù)

找到了tid的上限之后,我們的策略是從上限向0迭代,當然,要生成新的url,只需要對老的url串進行簡單的處理就OK了

def get_next_url(self, oldUrl):'''description: 返回下次迭代的url:param oldUrl: 上一個爬去過的url:return: 下次要爬取的url'''# 傳入的url格式:http://www.heartsong.top/forum.php?mod=viewthread&tid=34l = oldUrl.split('=') #用等號分割字符串oldID = int(l[2])newID = oldID - 1if newID == 0: # 如果tid迭代到0了,說明網(wǎng)站爬完,爬蟲可以結(jié)束了returnnewUrl = l[0] + "=" + l[1] + "=" + str(newID) #構(gòu)造出新的urlreturn str(newUrl) # 返回新的url
  • 12

迭代request請求

有了找到下一個url的函數(shù)之后,我們就可以在適當?shù)奈恢锰砑尤缦麓a,發(fā)起新的請求,“適當?shù)奈恢谩卑ㄒ韵聝煞N情況:
* 本頁的數(shù)據(jù)獲取完成
* 本頁被刪除,無內(nèi)容

# 發(fā)起下一個主題貼的請求 next_url = self.get_next_url(response.url) # response.url就是原請求的url if next_url != None: # 如果返回了新的urlyield Request(next_url, callback=self.parse, headers=self.headers,cookies=self.cookies, meta=self.meta)
  • 1

分析總頁數(shù)

打開一個有分頁的主題帖,和一個沒有分頁的主題貼,找不同


先判斷頁面內(nèi)有沒有分頁的框,通過之前介紹的檢查網(wǎng)頁元素的辦法找到總頁數(shù),通過XPath定位,然后通過一個簡單的正則把總頁數(shù)拿出來。

pages = selector.xpath('//*[@id="pgt"]/div/div/label/span') if pages: # 如果pages不是空列表,說明該主題帖分頁pages = pages[0].re(r'[0-9]+')[0] # 正則匹配出總頁數(shù)print "This post has", pages, "pages"
  • 1

迭代帶page參數(shù)的url

分析出了總頁數(shù)之后,無非就是拼接出子頁的url,然后發(fā)起Request請求,不過要注意,回調(diào)函數(shù)不能再是parse了,因為那樣的話會在這里無限的生成Request。所以我們需要自己定義一個函數(shù)sub_parse,去處理子頁的response。

# response.url格式: http://www.heartsong.top/forum.php?mod=viewthread&tid=34 # 子utl格式: http://www.heartsong.top/forum.php?mod=viewthread&tid=34&page=1 tmp = response.url.split('=') # 以=分割url # 循環(huán)生成所有子頁面的請求 for page_num in xrange(2, int(pages) + 1): # 構(gòu)造新的urlsub_url = tmp[0] + '=' + tmp[1] + '=' + tmp[2] + 'page=' + str(page_num)# 注意此處的回調(diào)函數(shù)是self.sub_parse,就是說這個請求的response會傳到# self.sub_parse里去處理yield Request(sub_url,callback=self.sub_parse, headers=self.headers,cookies=self.cookies, meta=self.meta)

sub_parse:

def sub_parse(self, response):"""用以爬取主題貼除首頁外的其他子頁:param response::return:"""selector = Selector(response)table = selector.xpath('//*[starts-with(@id, "pid")]') # 取出所有的樓層for each in table:item = HeartsongItem() # 實例化一個item# 通過XPath匹配信息,注意extract()方法返回的是一個listitem['author'] = each.xpath('tr[1]/td[@class="pls"]/div[@class="pls favatar"]/div[@class="pi"]/div[@class="authi"]/a/text()').extract()[0]item['post_time'] = each.xpath('tr[1]/td[@class="plc"]/div[@class="pi"]').re(r'[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+')[0]content_list = each.xpath('.//td[@class="t_f"]').xpath('string(.)').extract()content = "".join(content_list) # 將list轉(zhuǎn)化為stringitem['url'] = response.url # 用這種方式獲取網(wǎng)頁的url# 把內(nèi)容中的換行符,空格等去掉item['content'] = content.replace('\r\n', '').replace(' ', '').replace('\n', '')yield item # 將創(chuàng)建并賦值好的Item對象傳遞到PipeLine當中進行處理

完整代碼

settings.py,pipelines.py,item.py相較于第二節(jié)都沒有改動。
heart_song.py:

# -*- coding: utf-8 -*-# import scrapy # 可以用這句代替下面三句,但不推薦 from scrapy.spiders import Spider from scrapy.selector import Selector from scrapy import Request from heartsong.items import HeartsongItem # 如果報錯是pyCharm對目錄理解錯誤的原因,不影響class HeartsongSpider(Spider):name = "heartsong"allowed_domains = ["heartsong.top"] # 允許爬取的域名,非此域名的網(wǎng)頁不會爬取start_urls = [# 起始url,這里設(shè)置為從最大tid開始,向0的方向迭代"http://www.heartsong.top/forum.php?mod=viewthread&tid=34"]# 用來保持登錄狀態(tài),可把chrome上拷貝下來的字符串形式cookie轉(zhuǎn)化成字典形式,粘貼到此處cookies = {}# 發(fā)送給服務(wù)器的http頭信息,有的網(wǎng)站需要偽裝出瀏覽器頭進行爬取,有的則不需要headers = {# 'Connection': 'keep - alive','User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36'}# 對請求的返回進行處理的配置meta = {'dont_redirect': True, # 禁止網(wǎng)頁重定向'handle_httpstatus_list': [301, 302] # 對哪些異常返回進行處理}def get_next_url(self, oldUrl):'''description: 返回下次迭代的url:param oldUrl: 上一個爬去過的url:return: 下次要爬取的url'''# 傳入的url格式:http://www.heartsong.top/forum.php?mod=viewthread&tid=34l = oldUrl.split('=') #用等號分割字符串oldID = int(l[2])newID = oldID - 1if newID == 0: # 如果tid迭代到0了,說明網(wǎng)站爬完,爬蟲可以結(jié)束了returnnewUrl = l[0] + "=" + l[1] + "=" + str(newID) #構(gòu)造出新的urlreturn str(newUrl) # 返回新的urldef start_requests(self):"""這是一個重載函數(shù),它的作用是發(fā)出第一個Request請求:return:"""# 帶著headers、cookies去請求self.start_urls[0],返回的response會被送到# 回調(diào)函數(shù)parse中yield Request(self.start_urls[0],callback=self.parse, headers=self.headers,cookies=self.cookies, meta=self.meta)def parse(self, response):"""用以處理主題貼的首頁:param response::return:"""selector = Selector(response) # 創(chuàng)建選擇器table = selector.xpath('//*[starts-with(@id, "pid")]') # 取出所有的樓層if not table:# 這個鏈接內(nèi)沒有一個樓層,說明此主題貼可能被刪了,# 把這類url保存到一個文件里,以便審查原因print "bad url!"f = open('badurl.txt', 'a')f.write(response.url)f.write('\n')f.close()# 發(fā)起下一個主題貼的請求next_url = self.get_next_url(response.url) # response.url就是原請求的urlif next_url != None: # 如果返回了新的urlyield Request(next_url, callback=self.parse, headers=self.headers,cookies=self.cookies, meta=self.meta)returnfor each in table:item = HeartsongItem() # 實例化一個item# 通過XPath匹配信息,注意extract()方法返回的是一個listitem['author'] = each.xpath('tr[1]/td[@class="pls"]/div[@class="pls favatar"]/div[@class="pi"]/div[@class="authi"]/a/text()').extract()[0]item['post_time'] = each.xpath('tr[1]/td[@class="plc"]/div[@class="pi"]').re(r'[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+')[0]# XPath的string(.)用法,解決標簽套標簽的情況,具體解釋請自行找XPath教程content_list = each.xpath('.//td[@class="t_f"]').xpath('string(.)').extract()content = "".join(content_list) # 將list轉(zhuǎn)化為stringitem['url'] = response.url # 用這種方式獲取網(wǎng)頁的url# 把內(nèi)容中的換行符,空格等去掉item['content'] = content.replace('\r\n', '').replace(' ', '').replace('\n', '')yield item # 將創(chuàng)建并賦值好的Item對象傳遞到PipeLine當中進行處理pages = selector.xpath('//*[@id="pgt"]/div/div/label/span')if pages: # 如果pages不是空列表,說明該主題帖分頁pages = pages[0].re(r'[0-9]+')[0] # 正則匹配出總頁數(shù)print "This post has", pages, "pages"# response.url格式: http://www.heartsong.top/forum.php?mod=viewthread&tid=34# 子utl格式: http://www.heartsong.top/forum.php?mod=viewthread&tid=34&page=1tmp = response.url.split('=') # 以=分割url# 循環(huán)生成所有子頁面的請求for page_num in xrange(2, int(pages) + 1):# 構(gòu)造新的urlsub_url = tmp[0] + '=' + tmp[1] + '=' + tmp[2] + 'page=' + str(page_num)# 注意此處的回調(diào)函數(shù)是self.sub_parse,就是說這個請求的response會傳到# self.sub_parse里去處理yield Request(sub_url,callback=self.sub_parse, headers=self.headers,cookies=self.cookies, meta=self.meta)# 發(fā)起下一個主題貼的請求next_url = self.get_next_url(response.url) # response.url就是原請求的urlif next_url != None: # 如果返回了新的urlyield Request(next_url,callback=self.parse, headers=self.headers,cookies=self.cookies, meta=self.meta)def sub_parse(self, response):"""用以爬取主題貼除首頁外的其他子頁:param response::return:"""selector = Selector(response)table = selector.xpath('//*[starts-with(@id, "pid")]') # 取出所有的樓層for each in table:item = HeartsongItem() # 實例化一個item# 通過XPath匹配信息,注意extract()方法返回的是一個listitem['author'] = each.xpath('tr[1]/td[@class="pls"]/div[@class="pls favatar"]/div[@class="pi"]/div[@class="authi"]/a/text()').extract()[0]item['post_time'] = each.xpath('tr[1]/td[@class="plc"]/div[@class="pi"]').re(r'[0-9]+-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+')[0]content_list = each.xpath('.//td[@class="t_f"]').xpath('string(.)').extract()content = "".join(content_list) # 將list轉(zhuǎn)化為stringitem['url'] = response.url # 用這種方式獲取網(wǎng)頁的url# 把內(nèi)容中的換行符,空格等去掉item['content'] = content.replace('\r\n', '').replace(' ', '').replace('\n', '')yield item # 將創(chuàng)建并賦值好的Item對象傳遞到PipeLine當中進行處理
  • 124

運行

同教程二。區(qū)別在于爬的數(shù)據(jù)是多個帖子的數(shù)據(jù)。

小結(jié)

至此,一個較為完整的定向爬蟲已經(jīng)寫完了,項目地址https://github.com/kongtianyi/heartsong。接下來的教程中,我會介紹如何拓展功能。比如某些帖子內(nèi)容需要回復可見,我們需要爬蟲自動回復。再比如有些網(wǎng)站會檢測出你是爬蟲然后封你的IP,這時候就需要啟用代理。等等……

總結(jié)

以上是生活随笔為你收集整理的Scrapy定向爬虫教程(三)——爬取多个页面的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。