爬蟲之全站爬取方法
這是崔斯特的第六十九篇原創文章
方法
做過好幾個關於網站全站的專案,這裡總結一下。
先把上面那張圖寫下來,全站爬取的兩種方法:
- 關係網路:
- 優點:簡單;可以抓取“熱門”資料
- 缺點:無法抓取全量資料;速度慢;需要解決去重問題
- 可行性:比較高
- 遍歷ID
- 優點:可以抓取所有資料;不用資料去重
- 缺點:資源消耗大;速度慢;可能被發現
- 可行性:僅可用於ID自增
關於關係網路
其實這個很好理解。比如說知乎,一個大V有100W粉絲,從這個大V出發,抓取粉絲的粉絲,一直迴圈下去。(可能是個死迴圈)
這個方法就比較簡單, Scrapy
中就是繼承 CrawlSpider
,再編寫匹配規則就好。
Example
import scrapy from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor class MySpider(CrawlSpider): name = 'example.com' allowed_domains = ['example.com'] start_urls = ['http://www.example.com'] rules = ( # Extract links matching 'category.php' (but not matching 'subsection.php') # and follow links from them (since no callback means follow=True by default). Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))), # Extract links matching 'item.php' and parse them with the spider's method parse_item Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'), ) def parse_item(self, response): self.logger.info('Hi, this is an item page! %s', response.url) item = scrapy.Item() item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)') item['name'] = response.xpath('//td[@id="item_name"]/text()').extract() item['description'] = response.xpath('//td[@id="item_description"]/text()').extract() return item
這種方法一般是搜尋引擎會做的。而且抓取的內容基本是最多人看到的,所以月排在前面,和SEO有關。
但是這種方法的缺點也是很明顯的,最明顯的就是沒法抓全資料,像那種冷門的資料就沒法抓取到,速度也是比較慢的,必須儲存去重佇列,以防止重複抓取頁面。(瞭解下 Redis" target="_blank" rel="nofollow,noindex">布隆過濾器 )
如果對資料完整性要求沒那麼高可以考慮這種方法。
遍歷ID
找各種方法就比較無腦了,啥也不用想,從0開始遍歷跑吧。
毫無疑問,這種方法可以抓取網站所有的資料,因為在開始抓取前就已經完成的去重,所以這方面就不用管了。
但是缺點也很明顯,因為是遍歷ID,所以需要很多伺服器資源和代理資源,有可能某個ID已經下架或失效。所以整個工程請求量會非常大。而且可能被別人發現,一般人都去看那些熱門帖子,結果你把那麼重來沒人看的翻了一遍,別人也會發現資料異常的(也會存在假資料的情況:sob:)。
而且這種方法之適用於ID自增的,大多數是數字ID遞增,比如說天眼查的:
https://www.tianyancha.com/company/24762997 https://www.tianyancha.com/company/150041670 https://www.tianyancha.com/company/1073358312
知乎也是:
https://zhuanlan.zhihu.com/p/47969297 https://zhuanlan.zhihu.com/p/48652497 https://zhuanlan.zhihu.com/p/47805332
應該是和數字有關係,可以先採樣進行抓取,研究資料分佈情況。
當提供不正確ID時,也會返回資料不存在的情況
在這裡提供一個生成ID的方法
def gen_uid(num): """ 使用生成器生成ID :param num: 起始ID :return: 生成器 """ js = 0 result = list() while js < 20000: num += 1 js += 1 result.append(num) yield result
最後再看看這張圖。兩種方法都有優缺點,根據實際需求選取,如果你還知道別的抓取方法,歡迎指出。