爬虫教程( 6 ) --- 爬虫 进阶、扩展
1. 前言
1. 先看一個最簡單的爬蟲。
import requestsurl = "http://www.cricode.com" r = requests.get(url) print(r.text)2. 一個正常的爬蟲程序
上面那個最簡單的爬蟲,是一個不完整的殘疾的爬蟲。因為爬蟲程序通常需要做的事情如下:
- 1)給定的種子 URLs,爬蟲程序將所有種子 URL 頁面爬取下來
- 2)爬蟲程序解析爬取到的 URL 頁面中的鏈接,將這些鏈接放入待爬取 URL 集合中
- 3)重復 1、2 步,直到達到指定條件才結束爬取
因此,一個完整的爬蟲大概是這樣子的:
import requests # 用來爬取網頁 from bs4 import BeautifulSoup # 用來解析網頁# 我們的種子 seeds = ["http://www.hao123.com","http://www.csdn.net","http://www.cricode.com" ]# 設定終止條件為:爬取到 100000個頁面時就停止爬取 end_sum = 0def do_save_action(text=None):passwhile end_sum < 10000:if end_sum < len(seeds):r = requests.get(seeds[end_sum])end_sum = end_sum + 1do_save_action(r.text)soup = BeautifulSoup(r.content)urls = soup.find_all('a') # 解析網頁for url in urls:seeds.append(url)else:break3. 現在來找茬。上面那個完整的爬蟲,缺點實在是太多。下面一一列舉它的N宗罪:
- 1)我們的任務是爬取1萬個網頁,按上面這個程序,一個人在默默的爬取,假設爬起一個網頁3秒鐘,那么,爬一萬個網頁需要3萬秒鐘。MGD,我們應當考慮開啟多個線程(池)去一起爬取,或者用分布式架構去并發的爬取網頁。
- 2)種子URL 和 后續解析到的URL 都放在一個列表里,應該設計一個更合理的數據結構來存放這些待爬取的 URL ,比如:隊列或者優先隊列。( scrapy-redis 是 種子URL 在 redis的 list 里面,后續解析到的URL 在 隊列里面?)
- 3)對各個網站的 url,我們一視同仁,事實上,我們應當區別對待。大站好站優先原則應當予以考慮。
- 4)每次發起請求,都是根據 url 發起請求,而這個過程中會牽涉到 DNS 解析,將 url 轉換成 ip 地址。一個網站通常由成千上萬的 URL,因此,可以考慮將這些網站域名的 IP 地址進行緩存,避免每次都發起 DNS 請求,費時費力。
- 5)解析到網頁中的 urls 后,我們沒有做任何去重處理,全部放入待爬取的列表中。事實上,可能有很多鏈接是重復的,我們做了很多重復勞動。
- 6)…..
4.找了這么多茬后,現在討論一下問題的解決方案。
- 1)并行爬取問題。我們可以有多種方法去實現并行。多線程或者線程池方式,一個爬蟲程序內部開啟多個線程。同一臺機器開啟多個爬蟲程序,如此,我們就有N多爬取線程在同時工作。能大大減少時間。此外,當我們要爬取的任務特別多時,一臺機器、一個網點肯定是不夠的,我們必須考慮分布式爬蟲。常見的分布式架構有:主從(Master——Slave)架構、點對點(Peer to Peer)架構,混合架構等。說到分布式架構,那我們需要考慮的問題就有很多,我們需要分派任務,各個爬蟲之間需要通信合作,共同完成任務,不要重復爬取相同的網頁。分派任務我們要做到公平公正,就需要考慮如何進行負載均衡。負載均衡,我們第一個想到的就是Hash,比如根據網站域名進行hash。負載均衡分派完任務之后,千萬不要以為萬事大吉了,萬一哪臺機器掛了呢?原先指派給掛掉的哪臺機器的任務指派給誰?又或者哪天要增加幾臺機器,任務有該如何進行重新分配呢 ?一個比較好的解決方案是用一致性 Hash 算法。
- 2)待爬取網頁隊列。如何對待待抓取隊列,跟操作系統如何調度進程是類似的場景。不同網站,重要程度不同,因此,可以設計一個優先級隊列來存放待爬起的網頁鏈接。如此一來,每次抓取時,我們都優先爬取重要的網頁。當然,你也可以效仿操作系統的進程調度策略之多級反饋隊列調度算法。
- 3)DNS緩存。為了避免每次都發起DNS查詢,我們可以將DNS進行緩存。DNS緩存當然是設計一個hash表來存儲已有的域名及其IP。
- 4)網頁去重。說到網頁去重,第一個想到的是垃圾郵件過濾。垃圾郵件過濾一個經典的解決方案是 Bloom Filter(布隆過濾器)。布隆過濾器原理簡單來說就是:建立一個大的位數組,然后用多個 Hash 函數對同一個 url 進行 hash 得到多個數字,然后將位數組中這些數字對應的位置為1。下次再來一個url時,同樣是用多個Hash函數進行hash,得到多個數字,我們只需要判斷位數組中這些數字對應的為是全為1,如果全為1,那么說明這個url已經出現過。如此,便完成了url去重的問題。當然,這種方法會有誤差,只要誤差在我們的容忍范圍之類,比如1萬個網頁,我只爬取到了9999個,也是可以忍受滴。。。
- 5)數據存儲的問題。數據存儲同樣是個很有技術含量的問題。用關系數據庫存取還是用 NoSQL,或是自己設計特定的文件格式進行存儲,都大有文章可做。
- 6)進程間通信。分布式爬蟲,就必然離不開進程間的通信。我們可以以規定的數據格式進行數據交互,完成進程間通信。
- 7)……
如何實現上面這些東西 ???
實現的過程中,你會發現,我們要考慮的問題遠遠不止上面這些。紙上得來終覺淺,覺知此事要躬行!
2. 如何 "跟蹤"?和 "過濾"
在很多情況下,我們并不是只抓取某個頁面,而需要 "順藤摸瓜",從幾個種子頁面,通過超級鏈接索,最終定位到我們想要的頁面。Scrapy 對這個功能進行了很好的抽象:
from abc import ABCfrom scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor from scrapy.selector import Selector from scrapy.item import Itemclass Coder4Spider(CrawlSpider, ABC):name = 'coder4'allowed_domains = ['xxx.com']start_urls = ['http://www.xxx.com']rules = (Rule(LinkExtractor(allow=('page/[0-9]+',))),Rule(LinkExtractor(allow=('archives/[0-9]+',)), callback='parse_item'),)def parse_item(self, response):self.log(f'request url : {response.url}')在上面,我們用了 CrawlSpider 而不是 Spider。其中 name、 allowed_domains、start_urls 就不解釋了。
重點說下 Rule:
-
第 1 條不帶 callback 的,表示只是 “跳板”,即只下載網頁并根據 allow 中匹配的鏈接,去繼續遍歷下一步的頁面,實際上 Rule 還可以指定 deny=xxx 表示過濾掉哪些頁面。
-
第 2 條帶 callback 的,是最終會回調 parse_item 函數的網頁。
3. 如何 "過濾重復" 的頁面
Scrapy 支持通過 RFPDupeFilter 來完成頁面的去重(防止重復抓取)。
DUPEFILTER_CLASS = 'scrapy.dupefilters.RFPDupeFilter'RFPDupeFilter 實際是根據 request_fingerprint 實現過濾的。
源碼中實現如下:
def request_fingerprint(request, include_headers=None, keep_fragments=False):if include_headers:include_headers = tuple(to_bytes(h.lower()) for h in sorted(include_headers))cache = _fingerprint_cache.setdefault(request, {})cache_key = (include_headers, keep_fragments)if cache_key not in cache:fp = hashlib.sha1()fp.update(to_bytes(request.method))fp.update(to_bytes(canonicalize_url(request.url, keep_fragments=keep_fragments)))fp.update(request.body or b'')if include_headers:for hdr in include_headers:if hdr in request.headers:fp.update(hdr)for v in request.headers.getlist(hdr):fp.update(v)cache[cache_key] = fp.hexdigest()return cache[cache_key]我們可以看到,去重指紋是 sha1(method + url + body + header),所以,實際能夠去掉重復的比例并不大。如果我們需要自己提取去重的 finger,需要自己實現 Filter,并配置上它。下面這個 Filter 只根據 url 去重:
from scrapy.dupefilters import RFPDupeFilterclass SeenURLFilter(RFPDupeFilter):"""A dupe filter that considers the URL"""def __init__(self, path=None):self.urls_seen = set()RFPDupeFilter.__init__(self, path)def request_seen(self, request):if request.url in self.urls_seen:return Trueelse:self.urls_seen.add(request.url)不要忘記配置上:
DUPEFILTER_CLASS ='scraper.custom_filters.SeenURLFilter'4. 海量數據處理算法 Bloom Filter
海量數據處理算法—Bloom Filter:https://www.cnblogs.com/zhxshseu/p/5289871.html
結合 Guava 源碼解讀布隆過濾器:http://cyhone.com/2017/02/07/Introduce-to-BloomFilter/
更多:https://www.baidu.com/s?wd=Bloomfilter%20%E7%AE%97%E6%B3%95
Bloom-Filter,即布隆過濾器,1970年由 Bloom 中提出。是一種多哈希函數映射的快速查找算法。通常應用在一些需要快速判斷某個元素是否屬于集合,但是并不嚴格要求100%正確的場合。Bloom Filter 有可能會出現錯誤判斷,但不會漏掉判斷。也就是Bloom Filter 如果判斷元素不在集合中,那肯定就是不在。如果判斷元素存在集合中,有一定的概率判斷錯誤。。。
因此,Bloom Filter 不適合那些 "零錯誤"?的應用場合。而在能容忍低錯誤率的應用場合下,Bloom Filter 比其他常見的算法(如hash,折半查找)極大節省了空間。
- 優點是空間效率和查詢時間都遠遠超過一般的算法,
- 缺點是有一定的誤識別率和刪除困難。
一. 實例
為了說明 Bloom Filter 存在的重要意義,舉一個實例:假設要你寫一個網絡蜘蛛(web crawler)。由于網絡間的鏈接錯綜復雜,蜘蛛在網絡間爬行很可能會形成 “環”。為了避免形成“環”,就需要知道蜘蛛已經訪問過那些 URL。給一個 URL,怎樣知道蜘蛛是否已經訪問過呢?稍微想想,就會有如下幾種方案:
- 1. 將訪問過的 URL 保存到數據庫。
- 2. 用 HashSet 將訪問過的 URL 保存起來。那只需接近 O(1) 的代價就可以查到一個 URL 是否被訪問過了。
- 3. URL 經過 MD5 或 SHA-1 等單向哈希后再保存到 HashSet 或數據庫。
- 4. Bit-Map 方法。建立一個 BitSet,將每個 URL 經過一個哈希函數映射到某一位。
方法 1~3 都是將訪問過的 URL 完整保存,方法4 則只標記 URL 的一個映射位。以上方法在數據量較小的情況下都能完美解決問題,但是當數據量變得非常龐大時問題就來了。
- 方法 1 的 缺點:數據量變得非常龐大后關系型數據庫查詢的效率會變得很低。而且每來一個URL就啟動一次數據庫查詢是不是太小題大做了?
- 方法 2 的 缺點:太消耗內存。隨著 URL 的增多,占用的內存會越來越多。就算只有1億個 URL,每個 URL 只算 50 個字符,就需要 5GB 內存。
- 方法 3 :由于字符串經過 MD5 處理后的信息摘要長度只有128Bit,SHA-1 處理后也只有 160Bit,因此 方法3 比 方法2 節省了好幾倍的內存。
- 方法 4 :消耗內存是相對較少的,但缺點是單一哈希函數發生沖突的概率太高。還記得數據結構課上學過的 Hash 表沖突的各種解決方法么?若要降低沖突發生的概率到1%,就要將 BitSet 的長度設置為 URL 個數的 100 倍。
實質上,上面的算法都忽略了一個重要的隱含條件:允許小概率的出錯,不一定要100%準確!也就是說少量 url 實際上沒有沒網絡蜘蛛訪問,而將它們錯判為已訪問的代價是很小的——大不了少抓幾個網頁唄。
二. Bloom Filter 的算法
? ? ? ? 廢話說到這里,下面引入本篇的主角——Bloom Filter。其實上面方法4的思想已經很接近 Bloom Filter 了。方法四的致命缺點是沖突概率高,為了降低沖突的概念,Bloom Filter 使用了多個哈希函數,而不是一個。
? ? ? ? Bloom Filter 算法如下:創建一個 m位 BitSet,先將所有位初始化為0,然后選擇 k個 不同的哈希函數。第 i個 哈希函數對 字符串str 哈希的結果記為 h(i,str),且 h(i,str)的范圍是 0 到 m-1 。
- (1) 加入字符串過程。下面是每個字符串處理的過程,首先是將字符串 str “記錄” 到 BitSet 中的過程:對于字符串 str,分別計算 h(1,str),h(2,str)…… h(k,str)。然后將 BitSet 的第 h(1,str)、h(2,str)…… h(k,str)位設為1。下圖是 Bloom Filter 加入字符串過程,很簡單吧?這樣就將字符串 str 映射到 BitSet 中的 k 個二進制位了。
- (2) 檢查字符串是否存在的過程。下面是檢查字符串str是否被BitSet記錄過的過程:對于字符串 str,分別計算 h(1,str),h(2,str)…… h(k,str)。然后檢查 BitSet 的第 h(1,str)、h(2,str)…… h(k,str)位是否為1,若其中任何一位不為1則可以判定str一定沒有被記錄過。若全部位都是1,則 “認為” 字符串 str 存在。若一個字符串對應的 Bit 不全為1,則可以肯定該字符串一定沒有被 Bloom Filter 記錄過。(這是顯然的,因為字符串被記錄過,其對應的二進制位肯定全部被設為1了)。但是若一個字符串對應的Bit全為1,實際上是不能100%的肯定該字符串被 Bloom Filter 記錄過的。(因為有可能該字符串的所有位都剛好是被其他字符串所對應)這種將該字符串劃分錯的情況,稱為 false positive 。
三. Bloom Filter 參數選擇
- (1) 哈希函數選擇。哈希函數的選擇對性能的影響應該是很大的,一個好的哈希函數要能近似等概率的將字符串映射到各個Bit。選擇k個不同的哈希函數比較麻煩,一種簡單的方法是選擇一個哈希函數,然后送入k個不同的參數。
- (2) m,n,k 值,我們如何取值。我們定義:
可能把不屬于這個集合的元素誤認為屬于這個集合(False Positive)
不會把屬于這個集合的元素誤認為不屬于這個集合(False Negative)。
哈希函數的個數 k、位數組大小 m、加入的字符串數量 n 的關系。哈希函數個數k取10,位數組大小m設為字符串個數 n 的20倍時,false positive 發生的概率是0.0000889 ,即10萬次的判斷中,會存在 9 次誤判,對于一天1億次的查詢,誤判的次數為9000次。
哈希函數個數 k、位數組大小 m、加入的字符串數量 n 的關系可以參考參考文獻 (?http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html?)。
| m/n | k | k=17 | k=18 | k=19 | k=20 | k=21 | k=22 | k=23 | k=24 |
| 22 | 15.2 | 2.67e-05 | ? | ? | ? | ? | ? | ? | ? |
| 23 | 15.9 | 1.61e-05 | ? | ? | ? | ? | ? | ? | ? |
| 24 | 16.6 | 9.84e-06 | 1e-05 | ? | ? | ? | ? | ? | ? |
| 25 | 17.3 | 6.08e-06 | 6.11e-06 | 6.27e-06 | ? | ? | ? | ? | ? |
| 26 | 18 | 3.81e-06 | 3.76e-06 | 3.8e-06 | 3.92e-06 | ? | ? | ? | ? |
| 27 | 18.7 | 2.41e-06 | 2.34e-06 | 2.33e-06 | 2.37e-06 | ? | ? | ? | ? |
| 28 | 19.4 | 1.54e-06 | 1.47e-06 | 1.44e-06 | 1.44e-06 | 1.48e-06 | ? | ? | ? |
| 29 | 20.1 | 9.96e-07 | 9.35e-07 | 9.01e-07 | 8.89e-07 | 8.96e-07 | 9.21e-07 | ? | ? |
| 30 | 20.8 | 6.5e-07 | 6e-07 | 5.69e-07 | 5.54e-07 | 5.5e-07 | 5.58e-07 | ? | ? |
| 31 | 21.5 | 4.29e-07 | 3.89e-07 | 3.63e-07 | 3.48e-07 | 3.41e-07 | 3.41e-07 | 3.48e-07 | ? |
| 32 | 22.2 | 2.85e-07 | 2.55e-07 | 2.34e-07 | 2.21e-07 | 2.13e-07 | 2.1e-07 | 2.12e-07 | 2.17e-07 |
該文獻證明了對于給定的 m、n,當 k = ln(2)* m/n 時出錯的概率是最小的。(log2 e ≈ 1.44倍),同時該文獻還給出特定的k,m,n的出錯概率。例如:根據參考文獻1,哈希函數個數k取10,位數組大小m設為字符串個數n的20倍時,false positive 發生的概率是 0.0000889 ,這個概率基本能滿足網絡爬蟲的需求了。
四. Python 實現 Bloom filter
pybloomfiltermmap3? 和?pybloom 是 不同 的包。。。。。
Python3 安裝(?pybloomfiltermmap3 ):pip install pybloomfiltermmap3
- pybloomfiltermmap3 github 地址:https://pypi.org/project/pybloomfiltermmap3/
- pybloomfiltermmap3 官方文檔:https://pybloomfiltermmap3.readthedocs.io/en/latest/
- pybloomfiltermmap 的 github:https://github.com/axiak/pybloomfiltermmap
pybloomfiltermmap3?is a Python 3 compatible fork of?pybloomfiltermmap?by?@axiak。pybloomfiltermmap3 的目標:在 python3 中為 bloom過濾器 提供一個快速、簡單、可伸縮、正確的庫。
Python 中文網:https://www.cnpython.com/pypi/pybloomfiltermmap3
#################################################################
Windows 安裝報錯解決方法:Python - 安裝pybloomfilter遇到的問題及解決辦法:https://blog.csdn.net/tianbianEileen/article/details/75059132
Stack Overflow 上的回答如下:
this problem looks like one “sys/mman.h:No such file or directory” And is a Unix header and is not available on Windows.
I suggest you should ues pybloom instead on windows:
通過 pypi?搜索發現,最新的??pybloom 是?pybloom3 0.0.3
pybloom 的 github 地址:https://github.com/Hexmagic/pybloom3
所以安裝命令是:pip install pybloom3
and you should use the package like this:
from pybloom import BloomFilter#################################################################
pybloomfiltermmap3 快速示例:https://pybloomfiltermmap3.readthedocs.io/en/latest/
BloomFilter.copy_template(filename[, perm=0755]) → BloomFilter Creates a new BloomFilter object with the same parameters–same hash seeds, same size.. everything. Once this is performed, the two filters are comparable, so you can perform logical operators. Example:
>>> apple = BloomFilter(100, 0.1, '/tmp/apple') >>> apple.add('apple') False >>> pear = apple.copy_template('/tmp/pear') >>> pear.add('pear') False >>> pear |= appleBloomFilter.len(item) → Integer Returns the number of distinct elements that have been added to the BloomFilter object, subject to the error given in error_rate.
>>> bf = BloomFilter(100, 0.1, '/tmp/fruit.bloom') >>> bf.add("Apple") >>> bf.add('Apple') >>> bf.add('orange') >>> len(bf) 2 >>> bf2 = bf.copy_template('/tmp/new.bloom') >>> bf2 |= bf >>> len(bf2) Traceback (most recent call last):... pybloomfilter.IndeterminateCountError: Length of BloomFilter object is unavailable after intersection or union called.pybloom ?快速示例:
from pybloom import BloomFilter from pybloom import ScalableBloomFilterf = BloomFilter(capacity=1000, error_rate=0.001)print([f.add(x) for x in range(10)]) # [False, False, False, False, False, False, False, False, False, False]print(all([(x in f) for x in range(10)])) # Trueprint(10 in f) # Falseprint(5 in f) # Truef = BloomFilter(capacity=1000, error_rate=0.001) for i in range(0, f.capacity):_ = f.add(i) print((1.0 - (len(f) / float(f.capacity))) <= f.error_rate + 2e-18) # Truesbf = ScalableBloomFilter(mode=ScalableBloomFilter.SMALL_SET_GROWTH) count = 10000 for i in range(0, count):_ = sbf.add(i)print((1.0 - (len(sbf) / float(count))) <= sbf.error_rate + 2e-18) # True# len(sbf) may not equal the entire input length. 0.01% error is well # below the default 0.1% error threshold. As the capacity goes up, the # error will approach 0.1%.五:Bloom Filter 的優缺點。
- 優點:節約緩存空間(空值的映射),不再需要空值映射。減少數據庫或緩存的請求次數。提升業務的處理效率以及業務隔離性。
- 缺點:存在誤判的概率。傳統的 Bloom Filter 不能作刪除操作。
六:Bloom-Filter 的應用場景
Bloom-Filter 一般用于在大數據量的集合中判定某元素是否存在。
- (1) 適用于一些黑名單,垃圾郵件等的過濾,例如郵件服務器中的垃圾郵件過濾器。像網易,QQ這樣的公眾電子郵件(email)提供商,總是需要過濾來自發送垃圾郵件的人(spamer)的垃圾郵件。一個辦法就是記錄下那些發垃圾郵件的 email 地址。由于那些發送者不停地在注冊新的地址,全世界少說也有幾十億個發垃圾郵件的地址,將他們都存起來則需要大量的網絡服務器。如果用哈希表,每存儲一億個 email地址,就需要 1.6GB的內存(用哈希表實現的具體辦法是將每一個 email地址對應成一個八字節的信息指紋,然后將這些信息指紋存入哈希表,由于哈希表的存儲效率一般只有 50%,因此一個 email地址需要占用十六個字節。一億個地址大約要 1.6GB,即十六億字節的內存)。因此存貯幾十億個郵件地址可能需要上百 GB的內存。而 Bloom Filter 只需要哈希表 1/8 到 1/4 的大小就能解決同樣的問題。BloomFilter 決不會漏掉任何一個在黑名單中的可疑地址。而至于誤判問題,常見的補救辦法是在建立一個小的白名單,存儲那些可能別誤判的郵件地址。
- (2) 在搜索引擎領域,Bloom-Filte r最常用于網絡蜘蛛(Spider)的 URL 過濾,網絡蜘蛛通常有一個 URL 列表,保存著將要下載和已經下載的網頁的 URL,網絡蜘蛛下載了一個網頁,從網頁中提取到新的 URL 后,需要判斷該 URL 是否已經存在于列表中。此時,Bloom-Filter 算法是最好的選擇。
Google 的 BigTable。 Google 的 BigTable 也使用了 Bloom Filter,以減少不存在的行或列在磁盤上的查詢,大大提高了數據庫的查詢操作的性能。
key-value 加快查詢。
一般 Bloom-Filter 可以與一些 key-value 的數據庫一起使用,來加快查詢。一般 key-value 存儲系統的 values 存在硬盤,查詢就是件費時的事。將 Storage 的數據都插入Filter,在 Filter 中查詢都不存在時,那就不需要去Storage 查詢了。當 False Position 出現時,只是會導致一次多余的Storage查詢。
由于 Bloom-Filter 所用空間非常小,所有 BF 可以常駐內存。這樣子的話對于大部分不存在的元素,只需要訪問內存中的 Bloom-Filter 就可以判斷出來了,只有一小部分,需要訪問在硬盤上的 key-value 數據庫。從而大大地提高了效率。如圖:
5. scrapy_redis 去重優化 (?7億數據 )
原文鏈接:https://blog.csdn.net/Bone_ACE/article/details/53099042
使用布隆去重代替scrapy_redis(分布式爬蟲)自帶的dupefilter:https://blog.csdn.net/qq_36574108/article/details/82889744
背景:
前些天接手了上一位同事的爬蟲,一個全網爬蟲,用的是 scrapy + redis 分布式,任務調度用的 scrapy_redis 模塊。
大家應該知道 scrapy 是默認開啟了去重的,用了 scrapy_redis 后去重隊列放在 redis 里面,爬蟲已經有7億多條URL的去重數據了,再加上一千多萬條 requests 的種子,redis 占用了160多G的內存(服務器,Centos7),總共才175G好么。去重占用了大部分的內存,不優化還能跑?
一言不合就用 Bloomfilter+Redis 優化了一下,內存占用立馬降回到了二十多G,保證漏失概率小于萬分之一的情況下可以容納50億條URL的去重,效果還是很不錯的!在此記錄一下,最后附上 Scrapy+Redis+Bloomfilter 去重的 Demo(可將去重隊列和種子隊列分開!),希望對使用 scrapy 框架的朋友有所幫助。
記錄:
我們要優化的是去重,首先剝絲抽繭查看框架內部是如何去重的。
- 因為 scrapy_redis 會用自己 scheduler 替代 scrapy 框架的 scheduler 進行任務調度,所以直接去 scrapy_redis 模塊下查看scheduler.py 源碼即可。
-
在 open() 方法中有句:self.df = load_object(self.dupefilter_cls).from_spider(spider),其中?load_object(self.dupefilter_cls) 是根據對象的絕對路徑而載入一個對象并返回,self.dupefilter_cls 就是?SCHEDULER_DUPEFILTER_CLASS = 'scrapy_redis.dupefilter.RFPDupeFilter',from_spider(spider) 是返回一個??RFPDupeFilter類 的實例。
再看下面的 enqueue_request() 方法,
里面有句?if not request.dont_filter and self.df.request_seen(request) ,self.df.request_seen()這就是用來去重的了。按住Ctrl再左鍵點擊request_seen查看它的代碼,可看到下面的代碼:
首先得到一個 request 的指紋,然后使用 Redis 的 set 保存指紋??梢?scrapy_redis 是利用 set 數據結構來去重的,去重的對象是 request 的 fingerprint。至于這個 fingerprint 到底是什么,可以再深入去看 request_fingerprint() 方法的源碼(其實就是用 hashlib.sha1() 對 request 對象的某些字段信息進行壓縮)。我們用調試也可以看到,其實 fp 就是 request 對象加密壓縮后的一個字符串(40個字符,0~f)。
是否可用 Bloomfilter 進行優化?
以上步驟可以看出,我們只要在?request_seen()?方法上面動些手腳即可。由于現有的七億多去重數據存的都是這個 fingerprint,所有 Bloomfilter 去重的對象仍然是 request 對象的 fingerprint。更改后的代碼如下:
def request_seen(self, request):fp = request_fingerprint(request)if self.bf.isContains(fp): # 如果已經存在return Trueelse:self.bf.insert(fp)return Falseself.bf 是類 Bloomfilter() 的實例化,關于這個Bloomfilter()類,看下面的?基于 Redis 的 Bloomfilter 去重
以上,優化的思路和代碼就是這樣;以下將已有的七億多的去重數據轉成 Bloomfilter 去重。
- 內存將爆,動作稍微大點機器就能死掉,更別說Bloomfilter在上面申請內存了。當務之急肯定是將那七億多個fingerprint導出到硬盤上,而且不能用本機導,并且先要將redis的自動持久化給關掉。
- 因為常用Mongo,所以習慣性首先想到Mongodb,從redis取出2000條再一次性插入Mongo,但速度還是不樂觀,瓶頸在于MongoDB。(猜測是MongoDB對_id的去重導致的,也可能是物理硬件的限制)
- 后來想用SSDB,因為SSDB和Redis很相似,用list存肯定速度快很多。然而SSDB唯獨不支持Centos7,其他版本的系統都可。。
- 最后才想起來用txt,這個最傻的方法,卻是非常有效的方法。速度很快,只是為了防止讀取時內存不足,每100萬個fingerprint存在了一個txt,四臺機器txt總共有七百個左右。
- fingerprint取出來后redis只剩下一千多萬的Request種子,占用內存9G+。然后用Bloomfilter將txt中的fingerprint寫回Redis,寫完以后Redis占用內存25G,開啟redis自動持久化后內存占用49G左右。
6. 基于 Redis 的 Bloomfilter 去重
原文鏈接:http://blog.csdn.net/bone_ace/article/details/53107018
前言:
“去重” 是日常工作中會經常用到的一項技能,在爬蟲領域更是常用,并且規模一般都比較大。去重需要考慮兩個點:去重的數據量、去重速度。為了保持較快的去重速度,一般選擇在內存中進行去重。
- 數據量不大時,可以直接放在內存里面進行去重,例如 python 可以使用 set() 進行去重。
- 當去重數據需要持久化時可以使用 redis 的 set 數據結構。
- 當數據量再大一點時,可以用不同的加密算法先將長字符串壓縮成 16/32/40 個字符,再使用上面兩種方法去重;
- 當數據量達到億(甚至十億、百億)數量級時,內存有限,必須用 “位” 來去重,才能夠滿足需求。Bloomfilter 就是將去重對象映射到幾個內存“位”,通過幾個位的 0/1值來判斷一個對象是否已經存在。
- 然而 Bloomfilter 運行在一臺機器的內存上,不方便持久化(機器 down 掉就什么都沒啦),也不方便分布式爬蟲的統一去重。如果可以在 Redis 上申請內存進行 Bloomfilter,以上兩個問題就都能解決了。
本文即是用 Python 基于 Redis 實現 Bloomfilter 去重。下面先放代碼,最后附上說明。
代碼:
# encoding=utf-8import redis from hashlib import md5class SimpleHash(object):def __init__(self, cap, seed):self.cap = capself.seed = seeddef hash(self, value):ret = 0for i in range(len(value)):ret += self.seed * ret + ord(value[i])return (self.cap - 1) & retclass BloomFilter(object):def __init__(self, host='localhost', port=6379, db=0, blockNum=1, key='bloomfilter'):""":param host: the host of Redis:param port: the port of Redis:param db: witch db in Redis:param blockNum: one blockNum for about 90,000,000; if you have more strings for filtering, increase it.:param key: the key's name in Redis"""self.server = redis.Redis(host=host, port=port, db=db)# Redis 的 String 類型最大容量為512M,現使用 256M= 2^8 * 2^20 字節 = 2^28 * 2^3 bitself.bit_size = 1 << 31 self.seeds = [5, 7, 11, 13, 31, 37, 61]self.key = keyself.blockNum = blockNumself.hashfunc = []for seed in self.seeds:self.hashfunc.append(SimpleHash(self.bit_size, seed))def isContains(self, str_input):if not str_input:return Falsem5 = md5()m5.update(str_input.encode("utf8"))str_input = m5.hexdigest()ret = Truename = self.key + str(int(str_input[0:2], 16) % self.blockNum)for f in self.hashfunc:loc = f.hash(str_input)ret = ret & self.server.getbit(name, loc)return retdef insert(self, str_input):m5 = md5()m5.update(str_input.encode("utf8"))str_input = m5.hexdigest()name = self.key + str(int(str_input[0:2], 16) % self.blockNum)for f in self.hashfunc:loc = f.hash(str_input)self.server.setbit(name, loc, 1)if __name__ == '__main__':""" 第一次運行時會顯示 not exists!,之后再運行會顯示 exists! """bf = BloomFilter()if bf.isContains('http://www.baidu.com'): # 判斷字符串是否存在print('exists!')else:print('not exists!')bf.insert('http://www.baidu.com')說明:
需要提醒一下的是 Bloomfilter 算法會有漏失概率,即不存在的字符串有一定概率被誤判為已經存在。這個概率的大小與seeds 的數量、申請的內存大小、去重對象的數量有關。下面有一張表,m 表示內存大小(多少個位),n 表示去重對象的數量,k 表示seed的個數。例如我代碼中申請了256M,即1<<31(m=2^31,約21.5億。即 256 * 1024 *1024 * 8),seed設置了7個??磌=7那一列,當漏失率為8.56e-05時,m/n值為23。所以n = 21.5/23 = 0.93(億),表示漏失概率為 8.56e-05 時,256M 內存可滿足0.93億條字符串的去重。同理當漏失率為 0.000112 時,256M內存可滿足 0.98 億條字符串的去重。
基于 Redis 的 Bloomfilter 去重,其實就是利用了 Redis的String 數據結構,但 Redis 一個 String 最大只能 512M,所以如果去重的數據量大,需要申請多個去重塊(代碼中 blockNum 即表示去重塊的數量)。
代碼中使用了 MD5 加密壓縮,將字符串壓縮到了 32 個字符(也可用 hashlib.sha1()壓縮成40個字符)。
它有兩個作用,
-
一是 Bloomfilter 對一個很長的字符串哈希映射的時候會出錯,經常誤判為已存在,壓縮后就不再有這個問題;
-
二是壓縮后的字符為 0~f 共16中可能,我截取了前兩個字符,再根據blockNum將字符串指定到不同的去重塊進行去重。
總結:
基于 Redis 的 Bloomfilter 去重,既用上了 Bloomfilter 的海量去重能力,又用上了 Redis 的可持久化能力,基于 Redis 也方便分布式機器的去重。在使用的過程中,要預算好待去重的數據量,則根據上面的表,適當地調整 seed 的數量和 blockNum 數量(seed 越少肯定去重速度越快,但漏失率越大)。
7. scrapy_redis 種子優化
前言:
繼?scrapy_redis去重優化(已有7億條數據)【?https://blog.csdn.net/bone_ace/article/details/53099042 】,優化去重之后,Redis 的內存消耗降了許多,然而還不滿足。這次對 scrapy_redis 的種子隊列作了一些優化(嚴格來說并不能用上“優化”這詞,其實就是結合自己的項目作了一些改進,對本項目能稱作優化,對 scrapy_redis 未必是個優化)。
scrapy_redis 默認是將 Request 對象序列化后(變成一條字符串)存入 Redis 作為種子,需要的時候再取出來進行反序列化,還原成一個 Request 對象。
現在的問題是:序列化后的字符串太長,短則幾百個字符,長則上千。我的爬蟲平時至少也要維護包含幾千萬種子的種子隊列,占用內存在20G~50G之間(Centos)。想要縮減種子的長度,這樣不僅 Redis 的內存消耗會降低,各個 slaver 從 Redis 拿種子的速度也會有所提高,從而整個分布式爬蟲系統的抓取速度也會有所提高(效果視具體情況而定,要看爬蟲主要阻塞在哪里)。
記錄:
1、首先看調度器,即 scrapy_redis 模塊下的 scheduler.py 文件,可以看到?enqueue_request()方法和?next_request()方法就是種子 入隊列 和 出隊列 的地方,self.queue?指的是我們在 setting.py 里面設定的?SCHEDULER_QUEUE_CLASS?值,常用的是?'scrapy_redis.queue.SpiderPriorityQueue'。
2、進入 scrapy_redis 模塊下的 queue.py 文件,SpiderPriorityQueue 類的代碼如下:
class SpiderPriorityQueue(Base):"""Per-spider priority queue abstraction using redis' sorted set"""def __len__(self):"""Return the length of the queue"""return self.server.zcard(self.key)def push(self, request):"""Push a request"""data = self._encode_request(request)pairs = {data: -request.priority}self.server.zadd(self.key, **pairs)def pop(self, timeout=0):"""Pop a requesttimeout not support in this queue class"""pipe = self.server.pipeline()pipe.multi()pipe.zrange(self.key, 0, 0).zremrangebyrank(self.key, 0, 0)results, count = pipe.execute()if results:return self._decode_request(results[0])可以看到,上面用到了 Redis 的 zset 數據結構(它可以給種子加優先級),在進 Redis 之前用 _encode_request() 方法將Request 對象轉成字符串,_encode_request() 和 _decode_request 是 Base類下面的兩個方法:
def _encode_request(self, request):"""Encode a request object"""return pickle.dumps(request_to_dict(request, self.spider), protocol=-1)def _decode_request(self, encoded_request):"""Decode an request previously encoded"""return request_from_dict(pickle.loads(encoded_request), self.spider)可以看到,這里先將 Request 對象轉成一個字典,再將字典序列化成一個字符串。Request 對象怎么轉成一個字典呢?看下面的代碼,一目了然。
def request_to_dict(request, spider=None):"""Convert Request object to a dict.If a spider is given, it will try to find out the name of the spider methodused in the callback and store that as the callback."""cb = request.callbackif callable(cb):cb = _find_method(spider, cb)eb = request.errbackif callable(eb):eb = _find_method(spider, eb)d = {'url': to_unicode(request.url), # urls should be safe (safe_string_url)'callback': cb,'errback': eb,'method': request.method,'headers': dict(request.headers),'body': request.body,'cookies': request.cookies,'meta': request.meta,'_encoding': request._encoding,'priority': request.priority,'dont_filter': request.dont_filter,}return d調試截圖:(?注:d 為 Request 對象轉過來的字典,data 為字典序列化后的字符串。 )
3、了解完 scrapy_redis 默認的種子處理方式,現在針對自己的項目作一些調整。我的是一個全網爬蟲,每個種子需要記錄的信息主要有兩個:url 和 callback 函數名。此時我們選擇不用序列化,直接用簡單粗暴的方式,將 callback 函數名和 url 拼接成一條字符串作為一條種子,這樣種子的長度至少會減少一半。另外我們的種子并不需要設優先級,所以也不用 zset 了,改用 Redis 的list。以下是我新建的 SpiderSimpleQueue 類,加在 queue.py 中。如果在 settings.py 里將
SCHEDULER_QUEUE_CLASS?值設置成??'scrapy_redis.queue.SpiderSimpleQueue'?即可使用我這種野蠻粗暴的種子。
from scrapy.utils.reqser import request_to_dict, request_from_dict, _find_methodclass SpiderSimpleQueue(Base):""" url + callback """def __len__(self):"""Return the length of the queue"""return self.server.llen(self.key)def push(self, request):"""Push a request"""url = request.urlcb = request.callbackif callable(cb):cb = _find_method(self.spider, cb)data = '%s--%s' % (cb, url)self.server.lpush(self.key, data)def pop(self, timeout=0):"""Pop a request"""if timeout > 0:data = self.server.brpop(self.key, timeout=timeout)if isinstance(data, tuple):data = data[1]else:data = self.server.rpop(self.key)if data:cb, url = data.split('--', 1)try:cb = getattr(self.spider, str(cb))return Request(url=url, callback=cb)except AttributeError:raise ValueError("Method %r not found in: %s" % (cb, self.spider))__all__ = ['SpiderQueue', 'SpiderPriorityQueue', 'SpiderSimpleQueue', 'SpiderStack']4、另外需要提醒的是,如果 scrapy 中加了中間件?process_request(),當 yield 一個 Request 對象的時候,scrapy_redis 會直接將它丟進 Redis 種子隊列,未執行?process_requset();需要一個 Request 對象的時候,scrapy_redis 會從 Redis 隊列中取出種子,此時才會處理?process_request()方法,接著去抓取網頁。
所以并不需要擔心?process_request()里面添加的 Cookie 在 Redis 中放太久會失效,因為進 Redis 的時候它壓根都還沒執行process_request()。事實上 Request 對象序列化的時候帶上的字段很多都是沒用的默認字段,很多爬蟲都可以用 “callback+url” 的方式來優化種子。
5、最后,在 Scrapy_Redis_Bloomfilter ( https://github.com/LiuXingMing/Scrapy_Redis_Bloomfilter)這個 demo 中我已作了修改,大家可以試試效果。
結語:
經過以上優化,Redis 的內存消耗從 42G 降到了 27G!里面包含7億多條種子的去重數據 和 4000W+ 條種子。并且六臺子爬蟲的抓取速度都提升了一些。
兩次優化,內存消耗從160G+降到現在的27G,效果也是讓人滿意!
原文鏈接:http://blog.csdn.net/bone_ace/article/details/53306629
8. scrapy 引擎源碼解析
本節內容將介紹下 scrapy 引擎具體實現的功能。 engine.py 提供了2個類:Slot 和 ExecutionEngine
- Slot:? 提供了幾個方法,添加請求,刪除請求,關閉自己,觸發關閉方法。它使用 Twisted 的主循環 reactor 來不斷的調度執行 Engine 的 "_next_request" 方法,這個方法也是核心循環方法。
- ExecutionEngine:? 引擎的執行任務 。
爬蟲引擎 是 控制調度器,下載器?和 爬蟲 的。 This is the Scrapy engine which controls the Scheduler, Downloader and Spiders. For more information see docs/topics/architecture.rst
引擎 是 整個 scrapy 的核心控制和調度scrapy運行的。Engine 的 open_spider 方法完成了一些初始化,以及啟動調度器獲取種子隊列,以及去重隊列,最后調用 self._nest_request 開始一次爬取過程。
open_spider 中 slot 調用 _next_request,接下來我們看看 _next_request ,先是通過 _needs_backout(spider) 判斷是否需要結束爬蟲,然后返回,然后通過 self._next_request_from_scheduler(spider) 方法判斷是否還有 URL 需要去爬。
def _next_request(self, spider):slot = self.slotif not slot:returnif self.paused:return while not self._needs_backout(spider): # 是否需要返回if not self._next_request_from_scheduler(spider): # 是否還有 URL 需要爬取breakif slot.start_requests and not self._needs_backout(spider):try:request = next(slot.start_requests)except StopIteration:slot.start_requests = Noneexcept Exception:slot.start_requests = Nonelogger.error('Error while obtaining start requests',exc_info=True, extra={'spider': spider})else:self.crawl(request, spider)if self.spider_is_idle(spider) and slot.close_if_idle:self._spider_idle(spider)_next_request 循環通過 _next_request_from_scheduler(self,?spider) 方法從 scheduler 獲取下一個需要爬取的 request,然后送到下載器下載頁面。
def _next_request_from_scheduler(self, spider):slot = self.slotrequest = slot.scheduler.next_request() # 從隊列獲取下一個待爬取的 requestif not request:returnd = self._download(request, spider) # 使用 download 下載 requestd.addBoth(self._handle_downloader_output, request, spider) # 輸出下載的 responsed.addErrback(lambda f: logger.info('Error while handling downloader output',exc_info=failure_to_exc_info(f),extra={'spider': spider}))d.addBoth(lambda _: slot.remove_request(request))d.addErrback(lambda f: logger.info('Error while removing request from slot',exc_info=failure_to_exc_info(f),extra={'spider': spider}))d.addBoth(lambda _: slot.nextcall.schedule())d.addErrback(lambda f: logger.info('Error while scheduling new request',exc_info=failure_to_exc_info(f),extra={'spider': spider}))return d繼續看?_download(request, spider) 函數
ccdef _download(self, request, spider):slot = self.slotslot.add_request(request)def _on_success(response):assert isinstance(response, (Response, Request))if isinstance(response, Response): # 如果返回的是 Response 對象打印日志response.request = request # tie request to response receivedlogkws = self.logformatter.crawled(request, response, spider)logger.log(*logformatter_adapter(logkws), extra={'spider': spider})self.signals.send_catch_log(signal=signals.response_received, \response=response, request=request, spider=spider)return responsedef _on_complete(_):slot.nextcall.schedule()return _dwld = self.downloader.fetch(request, spider) # 使用downloader的fetch下載requestdwld.addCallbacks(_on_success) # 添加成功回掉方法dwld.addBoth(_on_complete)return dwldscrapy源碼分析 < 一 >:入口函數以及是如何運行
運行 scrapy crawl example 命令的時候,就會執行我們寫的爬蟲程序。
下面我們從源碼分析一下scrapy執行的流程:入口函數以及是如何運行:http://www.30daydo.com/article/530
Scrapy 閱讀源碼分析
原文鏈接:https://blog.csdn.net/weixin_37947156/category_6959928.html
scrapy 命令
當用 scrapy 寫好一個爬蟲后,使用?scrapy crawl <spider_name>命令就可以運行這個爬蟲,那么這個過程中到底發生了什么?
scrapy?命令從何而來? 實際上,當你成功安裝 scrapy 后,使用如下命令,就能找到這個命令:
$ which scrapy /usr/local/bin/scrapy使用?vim?或其他編輯器打開它:$ vim /usr/local/bin/scrapy 。其實它就是一個 python 腳本,而且代碼非常少。
#!/usr/bin/python # -*- coding: utf-8 -*- import re import sys from scrapy.cmdline import execute if __name__ == '__main__': sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) sys.exit(execute())安裝 scrapy 后,為什么入口點是這里呢? 原因是在 scrapy 的安裝文件?setup.py?中,聲明了程序的入口處:
from os.path import dirname, join from setuptools import setup, find_packageswith open(join(dirname(__file__), 'scrapy/VERSION'), 'rb') as f:version = f.read().decode('ascii').strip() setup(name='Scrapy',version=version,url='http://scrapy.org',description='A high-level Web Crawling and Screen Scraping framework',long_description=open('README.rst').read(),author='Scrapy developers',maintainer='Pablo Hoffman',maintainer_email='pablo@pablohoffman.com',license='BSD',packages=find_packages(exclude=('tests', 'tests.*')),include_package_data=True,zip_safe=False,entry_points={'console_scripts': ['scrapy = scrapy.cmdline:execute']},classifiers=['Framework :: Scrapy','Development Status :: 5 - Production/Stable','Environment :: Console','Intended Audience :: Developers','License :: OSI Approved :: BSD License','Operating System :: OS Independent','Programming Language :: Python','Programming Language :: Python :: 2','Programming Language :: Python :: 2.7','Topic :: Internet :: WWW/HTTP','Topic :: Software Development :: Libraries :: Application Frameworks','Topic :: Software Development :: Libraries :: Python Modules',],install_requires=['Twisted>=10.0.0','w3lib>=1.8.0','queuelib','lxml','pyOpenSSL','cssselect>=0.9','six>=1.5.2',], )entry_points?指明了入口是?cmdline.py?的?execute?方法,在安裝過程中,setuptools?這個包管理工具,就會把上述那一段代碼生成放在可執行路徑下。
入口(execute.py)
既然現在已經知道了 scrapy 的入口是?scrapy/cmdline.py?的?execute?方法,我們來看一下這個方法。
def execute(argv=None, settings=None):if argv is None:argv = sys.argvif settings is None:settings = get_project_settings()# set EDITOR from environment if availabletry:editor = os.environ['EDITOR']except KeyError:passelse:settings['EDITOR'] = editorinproject = inside_project()cmds = _get_commands_dict(settings, inproject)cmdname = _pop_command_name(argv)parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(),conflict_handler='resolve')if not cmdname:_print_commands(settings, inproject)sys.exit(0)elif cmdname not in cmds:_print_unknown_command(settings, cmdname, inproject)sys.exit(2)cmd = cmds[cmdname]parser.usage = "scrapy %s %s" % (cmdname, cmd.syntax())parser.description = cmd.long_desc()settings.setdict(cmd.default_settings, priority='command')cmd.settings = settingscmd.add_options(parser)opts, args = parser.parse_args(args=argv[1:])_run_print_help(parser, cmd.process_options, args, opts)cmd.crawler_process = CrawlerProcess(settings)_run_print_help(parser, _run_command, cmd, args, opts)sys.exit(cmd.exitcode)函數主要是初始化項目配置,在函數最后是初始化?CrawlerProcess?實例,然后運行對應命令實例的run方法。如果運行命令是scrapy crawl <spider_name>,則運行的就是?commands/crawl.py?的?run:
run方法中調用了?CrawlerProcess?實例的?crawl?和?start,就這樣整個爬蟲程序就會運行起來了。
再來看?CrawlerProcess 初始化。scrapy.core.engine.py( 其他代碼省略。。。?):
class CrawlerProcess(CrawlerRunner):def __init__(self, settings=None, install_root_handler=True):super(CrawlerProcess, self).__init__(settings)install_shutdown_handlers(self._signal_shutdown)configure_logging(self.settings, install_root_handler)log_scrapy_info(self.settings)構造方法中調用了父類?CrawlerRunner?的構造:
class CrawlerRunner:def __init__(self, settings=None):if isinstance(settings, dict) or settings is None:settings = Settings(settings)self.settings = settings# 獲取爬蟲加載器self.spider_loader = self._get_spider_loader(settings)self._crawlers = set()self._active = set()self.bootstrap_failed = Falseself._handle_twisted_reactor()初始化時,調用了?_get_spider_loader?方法:
默認配置文件中的?spider_loader?配置的是 scrapy.spiderloader.SpiderLoader:
爬蟲加載器會加載所有的爬蟲腳本,最后生成一個{spider_name: spider_cls}的字典。
執行 crawl 和 start 方法
CrawlerProcess?初始化完之后,調用?crawl方法:
這個過程會創建?Cralwer實例,然后調用它的?crawl方法,最后調用?start方法:
reactor?是個什么東西呢?它是?Twisted模塊的事件管理器,只要把需要執行的事件方法注冊到reactor中,然后調用它的run方法,它就會幫你執行注冊好的事件方法,如果遇到網絡IO等待,它會自動幫你切換可執行的事件方法,非常高效。
大家不用在意reactor是如何工作的,你可以把它想象成一個線程池,只是采用注冊回調的方式來執行事件。
到這里,爬蟲的之后調度邏輯就交由引擎ExecuteEngine處理了。
scrapy 源碼分析( 系列?):https://blog.csdn.net/happyAnger6/category_6085726_2.html
scrapy 是一個基于 twisted 實現的開源爬蟲,要讀懂其源碼,需要對twisted的異步編程模型有一定了解??梢酝ㄟ^之前3篇deferred的相關教程了解。
下面是總結的執行一個爬蟲任務的整體執行流程,請將圖片放大查看,即運行"scrapy crawl? xxxSpider"的執行流程:
流程中主要的顏色框的含義如下 :
- 1.紅色框是模塊或者類。
- 2.紫色框是向模塊或者類發送的消息,一般為函數調用。
- 3.紅色框垂直以下的黑色框即為本模塊或者對象執行流程的偽代碼描述。
幾個關鍵的模塊和類介紹如下:
- cmdline:命令行執行模塊,主要用于配置的獲取,并執行相應的ScrapyCommand。
- ScrapyCommand:命令對象,用于執行不同的命令。對于crawl任務,主要是調用CrawlerProcess的crawl和start方法。
- CrawlerProcess:顧名思義,爬取進程,主要用于管理Crawler對象,可以控制多個Crawler對象來同時進行多個不同的爬取任務,并調用Crawler的crawl方法。
- Crawler:爬取對象,用來控制爬蟲的執行,里面會通過一個執行引擎engine對象來控制spider從打開到啟動等生命周期。
- ExecutionEngine:執行引擎,主要控制整個調度過程,通過twisted的task.LoopingCall來不斷的產生爬取任務。
scrapy 源碼解析
<scrapy>scrapy源碼剖析:https://www.cnblogs.com/shuimohei/p/13363462.html
scrapy 源碼解析 (一):啟動流程源碼分析(一)命令行啟動:https://www.cnblogs.com/qiu-hua/p/12930422.html
scrapy 源碼解析 (二):啟動流程源碼分析(二) CrawlerProcess 主進程:https://www.cnblogs.com/qiu-hua/p/12930707.html
scrapy 源碼解析 (三):啟動流程源碼分析(三) ExecutionEngine 執行引擎:https://www.cnblogs.com/qiu-hua/p/12930803.html
scrapy 源碼解析 (四):啟動流程源碼分析(四) Scheduler調度器:https://www.cnblogs.com/qiu-hua/p/12932254.html
scrapy 源碼解析 (五):啟動流程源碼分析(五) Scraper刮取器:https://www.cnblogs.com/qiu-hua/p/12932818.html
Python 之 Scrapy 框架源碼解析:https://blog.csdn.net/cui_yonghua/article/details/107040329
scrapy 信號
官網說明:https://docs.scrapy.org/en/latest/topics/signals.html
scrapy 基礎組件專題(四):信號運用:https://www.cnblogs.com/qiu-hua/p/12638683.html
scrapy 的信號(signal)以及對下載中間件的一些總結:https://blog.csdn.net/fiery_heart/article/details/82229871
9. DNS 解析緩存
原文鏈接:https://blog.csdn.net/bone_ace/article/details/55000101
前言:
這是 Python 爬蟲中 DNS 解析緩存模塊中的核心代碼,是去年的代碼了,現在放出來 有興趣的可以看一下。
一般一個域名的 DNS 解析時間在 10~60 毫秒之間,這看起來是微不足道,但是對于大型一點的爬蟲而言這就不容忽視了。例如我們要爬新浪微博,同個域名下的請求有1千萬(這已經不算多的了),那么耗時在 10~60 萬秒之間,一天才 86400 秒。也就是說單 DNS 解析這一項就用了好幾天時間,此時加上 DNS 解析緩存,效果就明顯了。
下面直接放代碼,說明在后面。
代碼:
# encoding=utf-8 # --------------------------------------- # 版本:0.1 # 日期:2016-04-26 # 作者:九茶<bone_ace@163.com> # 開發環境:Win64 + Python 2.7 # ---------------------------------------import socket # from gevent import socket_dnscache = {}def _setDNSCache():""" DNS緩存 """def _getaddrinfo(*args, **kwargs):if args in _dnscache:# print str(args) + " in cache"return _dnscache[args]else:# print str(args) + " not in cache"_dnscache[args] = socket._getaddrinfo(*args, **kwargs)return _dnscache[args]if not hasattr(socket, '_getaddrinfo'):socket._getaddrinfo = socket.getaddrinfosocket.getaddrinfo = _getaddrinfo說明:
其實也沒什么難度,就是將 socket 里面的緩存保存下來,避免重復獲取。可以將上面的代碼放在一個 dns_cache.py 文件里,爬蟲框架里調用一下這個?_setDNSCache()方法就行了。
需要說明一下的是,如果你使用了 gevent 協程,并且用上了 monkey.patch_all(),要注意此時爬蟲已經改用 gevent 里面的socket 了,DNS 解析緩存模塊也應該要用 gevent 的 socket 才行。
10. Scrapy cookies 淺析
首先打消大家的疑慮,?Scrapy 會自動管理 cookies,?就像瀏覽器一樣:
Does Scrapy manage cookies automatically?
Yes, Scrapy receives and keeps track of cookies sent by servers, and sends them back on subsequent requests, like any regular web browser does.
Cookies 的管理是通過 CookiesMiddleware,?它屬于 DownloadMiddleware 的一部分,所有的 requests 和 response 都要經過它的處理。
首先看下處理 request 的部分,代碼如下:
class CookiesMiddleware(object):"""This middleware enables working with sites that need cookies"""def __init__(self, debug=False):# 用字典生成多個cookiesjarself.jars = defaultdict(CookieJar)self.debug = debugdef process_request(self, request, spider):if request.meta.get('dont_merge_cookies', False):return# 每個cookiesjar的key都存儲在 meta字典中cookiejarkey = request.meta.get("cookiejar")jar = self.jars[cookiejarkey]cookies = self._get_request_cookies(jar, request)# 把requests的cookies存儲到cookiesjar中for cookie in cookies:jar.set_cookie_if_ok(cookie, request)# set Cookie header# 刪除原有的cookiesrequest.headers.pop('Cookie', None)# 添加cookiesjar中的cookies到requests headerjar.add_cookie_header(request)self._debug_cookie(request, spider)流程如下:
- 使用字典初始化多個 cookies jar
- 把每個 requests 指定的 cookies jar 提取出來
- 然后根據 policy 把 requests 中的 cookies 添加 cookies jar
- 最后把 cookies jar 中合適的 cookies 添加到 requests 首部
接下來看看如何處理 response 中的 cookies:
def process_response(self, request, response, spider):if request.meta.get('dont_merge_cookies', False):return response# extract cookies from Set-Cookie and drop invalid/expired cookiescookiejarkey = request.meta.get("cookiejar")jar = self.jars[cookiejarkey]jar.extract_cookies(response, request)self._debug_set_cookie(response, spider)return response流程如下:
- 首先從 cookies jar 字典中把 requests 對應的 cookiesjar 提取出來.
- 使用 extract_cookies 把 response 首部中的 cookies 添加到 cookies jar
11. 擴展部分
python 之 goose3 庫?--- 文章提取工具
github 地址:https://github.com/goose3/goose3
goose3 官網文檔:https://goose3.readthedocs.io/en/latest/
goose3 是什么?
GOOSE3 最初是用 Java 編寫的一篇文章提取器,最近將它(Auff2011)轉換成Scala項目,這是 python 中的完全重寫。該軟件的目標是獲取任何新聞文章或文章類型的網頁,不僅提取文章的主體,而且還提取所有元數據和圖片。
官方網站:https://github.com/goose3/goose3
安裝
- pip install goose3
- mkvirtualenv --no-site-packages goose3 git clone https://github.com/goose3/goose3.git cd goose3 pip install -r ./requirements/python python setup.py install
使用:
示例用法:https://pypi.org/project/goose3/3.0.4/
python 之 goose3 庫:https://blog.csdn.net/weixin_42547344/article/details/100735035
動手實踐 CURL
CURL 是利用 URL 語法在命令行方式下工作的開源文件傳輸工具。它支持http、https、ftp、ftps、telnet 等多種協議,常被用來抓取網頁和監控Web服務器狀態。
CURL 使用
curl 命令可以用來構造http請求。
通用語法:curl [option] [URL...]
使用實例
curl 是 Linux下一個很強大的 http 命令行工具,其功能十分強大。
基本用法
curl http://www.baidu.com抓取 www.ip138.com 查詢網: 如發現亂碼,可以使用iconv轉碼:
curl http://ip138.com|iconv -f gb2312回車之后,html顯示在屏幕上了 ~
1.1 get方式提交數據:
curl -G -d "name=value&name2=value2" http://www.baidu.com1.2 post方式提交數據:
curl -d "name=value&name2=value2" http://www.baidu.com #post數據 curl -d a=b&c=d&txt@/tmp/txt http://www.baidu.com #post文件以表單的方式上傳文件:
curl -F file=@/tmp/me.txt http://www.aiezu.com相當于設置form表單的method="POST"和enctype='multipart/form-data'兩個屬性。
保存訪問的網頁
curl http://www.baidu.com > page.html或者用 curl 的內置 option 就好,存下 http 的結果,用這個 option: -o
curl -o page.html http://www.baidu.com curl -O http://sh.meituan.com/shop/42030772% Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left Speed 100 159k 0 159k 0 0 328k 0 --:--:-- --:--:-- --:--:-- 328k下載過程中標準輸出還會顯示下載的統計信息,比如進度、下載字節數、下載速度等
這樣,自動保存文件42030772,看到屏幕上出現一個下載頁面進度指示。顯示100%則表示保存成功
設置 Header
curl -H 'Host: 157.166.226.25'-H 'Accept-Language: es'-H 'Cookie: ID=1234' http://cnn.com顯示文檔信息
-I:顯示文檔信息
curl -I http://www.sina.com.cn/ -H Accept-Encoding:gzip,defalte指定proxy服務器以及其端口
-x :可以指定http訪問所使用的proxy服務器及其端口
curl -x 123.45.67.89:1080 -o page.html http://www.linuxidc.com curl -x http://username:pwd@ip:port http://www.baidu.com使用cookie
有些網站是使用cookie來記錄session信息。對于chrome這樣的瀏覽器,可以輕易處理cookie信息,但在curl中只要增加相關參數也是可以很容易的處理cookie
-c: 保存http的response里面的cookie信息。
curl -c cookiec.txt http://www.baidu.com執行后cookie信息就被存到了cookiec.txt里面了
-D: 保存http的response里面的header信息
curl -D cookied.txt http://www.baidu.com執行后cookie信息就被存到了cookied.txt里面了
注意:-c(小寫)產生的cookie和-D里面的cookie是不一樣的。
-b:使用cookie
很多網站都是通過監視你的cookie信息來判斷你是否按規矩訪問他們的網站的,因此我們需要使用保存的cookie信息。
curl -b cookiec.txt http://www.baidu.com模仿瀏覽器信息
有些網站需要使用特定的瀏覽器去訪問他們,有些還需要使用某些特定的版本。
-A :指定瀏覽器去訪問網站
curl -A "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.0)" http://www.baidu.com這樣服務器端就會認為是使用IE8.0去訪問的
偽造referer(盜鏈)
很多服務器會檢查http訪問的referer從而來控制訪問。比如:你是先訪問首頁,然后再訪問首頁中的郵箱頁面,這里訪問郵箱的referer地址就是訪問首頁成功后的頁面地址,如果服務器發現對郵箱頁面訪問的referer地址不是首頁的地址,就斷定那是個盜連了
-e:設定referer
curl -e "www.baidu.com" http://news.baidu.com/這樣就會讓服務器其以為你是從www.baidu.com點擊某個鏈接過來的
下載文件
9.1 -o/-O文件下載
-o: 把輸出寫到該文件中
curl -o dodo1.jpg http://www.shaimn.com/uploads/allimg/160613/1-160613121111.jpg-O:把輸出寫到該文件中,保留遠程文件的文件名
curl -O http://www.shaimn.com/uploads/allimg/160613/1-160613121111.jpg這樣就會以服務器上的名稱保存文件到本地
9.2 循環下載
有時候下載圖片可以能是前面的部分名稱是一樣的,就最后的尾椎名不一樣
curl -O http://www.shaimn.com/uploads/allimg/160613/1-16061312111[1-5].jpg這樣就會把
1-16061312111.jpg、
1-16061312112.jpg、
1-16061312113.jpg、
1-16061312114.jpg、
1-16061312115.jpg 全部保存下來
9.3 下載重命名
curl http://www.shaimn.com/uploads/allimg/160613/1-16061312111[1-5].jpg -o dodo#1.jpgdodo1.jpg,dodo2.jpg,dodo3.jpg,dodo4.jpg,dodo5.jpg
9.4 分塊下載
有時候下載的東西會比較大,這個時候我們可以分段下載。使用內置option:-r
curl -r 0-100 -o dodo1_part1.JPG http://www.shaimn.com/uploads/allimg/160613/1-160613121111.jpg curl -r 100-200 -o dodo1_part2.JPG http://www.shaimn.com/uploads/allimg/160613/1-160613121111.jpg curl -r 200- -o dodo1_part3.JPG http://www.shaimn.com/uploads/allimg/160613/1-160613121111.jpg cat dodo1_part* > dodo1.JPG這樣就可以查看dodo1.JPG的內容了
9.5 通過ftp下載文件
curl可以通過ftp下載文件,curl提供兩種從ftp中下載的語法
curl -O -u 用戶名:密碼 ftp://www.linux.com/dodo1.JPG curl -O ftp://用戶名:密碼@www.linux.com/dodo1.JPG9.6 顯示下載進度條
curl -# -O http://www.linux.com/dodo1.JPG9.7:不會顯示下載進度信息
curl -s -O http://www.linux.com/dodo1.JPG斷點續傳
在windows中,我們可以使用迅雷這樣的軟件進行斷點續傳。
curl可以通過內置option:-C同樣可以達到相同的效果
如果在下載dodo1.JPG的過程中突然掉線了,可以使用以下的方式續傳
上傳文件
curl不僅僅可以下載文件,還可以上傳文件。通過內置option:-T來實現
# curl -T dodo1.JPG -u 用戶名:密碼 ftp://www.linux.com/img/這樣就向ftp服務器上傳了文件dodo1.JPG
顯示抓取錯誤
# curl -f http://www.linux.com/error對 CURL 使用網絡限速
通過--limit-rate選項對CURL的最大網絡使用進行限制
下載速度最大不會超過1000B/secondcurl --limit-rate 1000B -O http://www.gnu.org/software/gettext/manual/gettext.htmllinux curl 命令
-a/--append 上傳文件時,附加到目標文件 -A/--user-agent <string> 設置用戶代理發送給服務器 - anyauth 可以使用“任何”身份驗證方法 -b/--cookie <name=string/file> cookie字符串或文件讀取位置 - basic 使用HTTP基本驗證 -B/--use-ascii 使用ASCII /文本傳輸 -c/--cookie-jar <file> 操作結束后把cookie寫入到這個文件中 -C/--continue-at <offset> 斷點續轉 -d/--data <data> HTTP POST方式傳送數據 --data-ascii <data> 以ascii的方式post數據 --data-binary <data> 以二進制的方式post數據 --negotiate 使用HTTP身份驗證 --digest 使用數字身份驗證 --disable-eprt 禁止使用EPRT或LPRT --disable-epsv 禁止使用EPSV -D/--dump-header <file> 把header信息寫入到該文件中 --egd-file <file> 為隨機數據(SSL)設置EGD socket路徑 --tcp-nodelay 使用TCP_NODELAY選項 -e/--referer 來源網址 -E/--cert <cert[:passwd]> 客戶端證書文件和密碼 (SSL) --cert-type <type> 證書文件類型 (DER/PEM/ENG) (SSL) --key <key> 私鑰文件名 (SSL) --key-type <type> 私鑰文件類型 (DER/PEM/ENG) (SSL) --pass <pass> 私鑰密碼 (SSL) --engine <eng> 加密引擎使用 (SSL). "--engine list" for list --cacert <file> CA證書 (SSL) --capath <directory> CA目錄 (made using c_rehash) to verify peer against (SSL) --ciphers <list> SSL密碼 --compressed 要求返回是壓縮的形勢 (using deflate or gzip) --connect-timeout <seconds> 設置最大請求時間 --create-dirs 建立本地目錄的目錄層次結構 --crlf 上傳是把LF轉變成CRLF -f/--fail 連接失敗時不顯示http錯誤 --ftp-create-dirs 如果遠程目錄不存在,創建遠程目錄 --ftp-method [multicwd/nocwd/singlecwd] 控制CWD的使用 --ftp-pasv 使用 PASV/EPSV 代替端口 --ftp-skip-pasv-ip 使用PASV的時候,忽略該IP地址 --ftp-ssl 嘗試用 SSL/TLS 來進行ftp數據傳輸 --ftp-ssl-reqd 要求用 SSL/TLS 來進行ftp數據傳輸 -F/--form <name=content> 模擬http表單提交數據 -form-string <name=string> 模擬http表單提交數據 -g/--globoff 禁用網址序列和范圍使用{}和[] -G/--get 以get的方式來發送數據 -h/--help 幫助 -H/--header <line>自定義頭信息傳遞給服務器 --ignore-content-length 忽略的HTTP頭信息的長度 -i/--include 輸出時包括protocol頭信息 -I/--head 只顯示文檔信息 從文件中讀取-j/--junk-session-cookies忽略會話Cookie - 界面<interface>指定網絡接口/地址使用 - krb4 <級別>啟用與指定的安全級別krb4 -j/--junk-session-cookies 讀取文件進忽略session cookie --interface <interface> 使用指定網絡接口/地址 --krb4 <level> 使用指定安全級別的krb4 -k/--insecure 允許不使用證書到SSL站點 -K/--config 指定的配置文件讀取 -l/--list-only 列出ftp目錄下的文件名稱 --limit-rate <rate> 設置傳輸速度 --local-port<NUM> 強制使用本地端口號 -m/--max-time <seconds> 設置最大傳輸時間 --max-redirs <num> 設置最大讀取的目錄數 --max-filesize <bytes> 設置最大下載的文件總量 -M/--manual 顯示全手動 -n/--netrc 從netrc文件中讀取用戶名和密碼 --netrc-optional 使用 .netrc 或者 URL來覆蓋-n --ntlm 使用 HTTP NTLM 身份驗證 -N/--no-buffer 禁用緩沖輸出 -o/--output 把輸出寫到該文件中 -O/--remote-name 把輸出寫到該文件中,保留遠程文件的文件名 -p/--proxytunnel 使用HTTP代理 --proxy-anyauth 選擇任一代理身份驗證方法 --proxy-basic 在代理上使用基本身份驗證 --proxy-digest 在代理上使用數字身份驗證 --proxy-ntlm 在代理上使用ntlm身份驗證 -P/--ftp-port <address> 使用端口地址,而不是使用PASV -Q/--quote <cmd>文件傳輸前,發送命令到服務器 -r/--range <range>檢索來自HTTP/1.1或FTP服務器字節范圍 --range-file 讀取(SSL)的隨機文件 -R/--remote-time 在本地生成文件時,保留遠程文件時間 --retry <num> 傳輸出現問題時,重試的次數 --retry-delay <seconds> 傳輸出現問題時,設置重試間隔時間 --retry-max-time <seconds> 傳輸出現問題時,設置最大重試時間 -s/--silent靜音模式。不輸出任何東西 -S/--show-error 顯示錯誤 --socks4 <host[:port]> 用socks4代理給定主機和端口 --socks5 <host[:port]> 用socks5代理給定主機和端口 --stderr <file> -t/--telnet-option <OPT=val> Telnet選項設置 --trace <file> 對指定文件進行debug --trace-ascii <file> Like --跟蹤但沒有hex輸出 --trace-time 跟蹤/詳細輸出時,添加時間戳 -T/--upload-file <file> 上傳文件 --url <URL> Spet URL to work with -u/--user <user[:password]>設置服務器的用戶和密碼 -U/--proxy-user <user[:password]>設置代理用戶名和密碼 -v/--verbose -V/--version 顯示版本信息 -w/--write-out [format]什么輸出完成后 -x/--proxy <host[:port]>在給定的端口上使用HTTP代理 -X/--request <command>指定什么命令 -y/--speed-time 放棄限速所要的時間。默認為30 -Y/--speed-limit 停止傳輸速度的限制,速度時間'秒 -z/--time-cond 傳送時間設置 -0/--http1.0 使用HTTP 1.0 -1/--tlsv1 使用TLSv1(SSL) -2/--sslv2 使用SSLv2的(SSL) -3/--sslv3 使用的SSLv3(SSL) --3p-quote like -Q for the source URL for 3rd party transfer --3p-url 使用url,進行第三方傳送 --3p-user 使用用戶名和密碼,進行第三方傳送 -4/--ipv4 使用IP4 -6/--ipv6 使用IP6 -#/--progress-bar 用進度條顯示當前的傳送狀態一些常見的限制方式
上述都是講的都是一些的基礎的知識,現在我就列一些比較常見的限制方式,如何突破這些限制抓取數據。
Basic Auth 一般會有用戶授權的限制,會在headers的Autheration字段里要求加入;
Referer 通常是在訪問鏈接時,必須要帶上Referer字段,服務器會進行驗證,例如抓取京東的評論;
User-Agent 會要求真是的設備,如果不加會用編程語言包里自有User-Agent,可以被辨別出來;
Cookie 一般在用戶登錄或者某些操作后,服務端會在返回包中包含Cookie信息要求瀏覽器設置Cookie,沒有Cookie會很容易被辨別出來是偽造請求;
也有本地通過JS,根據服務端返回的某個信息進行處理生成的加密信息,設置在Cookie里面;
Gzip 請求headers里面帶了gzip,返回有時候會是gzip壓縮,需要解壓;
JavaScript 加密操作 一般都是在請求的數據包內容里面會包含一些被javascript進行加密限制的信息,例如新浪微博會進行SHA1和RSA加密,之前是兩次SHA1加密,然后發送的密碼和用戶名都會被加密;
其他字段 因為http的headers可以自定義地段,所以第三方可能會加入了一些自定義的字段名稱或者字段值,這也是需要注意的。
真實的請求過程中,其實不止上面某一種限制,可能是幾種限制組合在一次,比如如果是類似RSA加密的話,可能先請求服務器得到Cookie,然后再帶著Cookie去請求服務器拿到公鑰,然后再用js進行加密,再發送數據到服務器。所以弄清楚這其中的原理,并且耐心分析很重要。
防封禁策略
Scrapy:
http://doc.scrapy.org/en/master/topics/practices.html#avoiding-getting-banned
如何讓你的 scrapy 爬蟲不再被 ban
根據 scrapy 官方文檔:https://docs.scrapy.org/en/master/topics/practices.html#avoiding-getting-banned?里面的描述,要防止 scrapy 被 ban,主要有以下幾個策略。
- 動態設置 user agent
- 禁用 cookies
- 設置延遲下載
- 使用?Google cache
- 使用IP地址池(Tor project、VPN和代理IP)
- 使用?Crawlera
由于 Google cache 受國內網絡的影響,你懂得; 所以主要從動態隨機設置user agent、禁用cookies、設置延遲下載和使用代理IP這幾個方式。
本文以?cnblogs 為例
創建 middlewares.py
scrapy代理IP、user agent 的切換都是通過 DOWNLOADER_MIDDLEWARES 進行控制,下面我們創建 middlewares.py文件。
[root@bogon cnblogs]# vi cnblogs/middlewares.py如下內容:
import random import base64 from settings import PROXIESclass RandomUserAgent(object):"""Randomly rotate user agents based on a list of predefined ones"""def __init__(self, agents):self.agents = agents@classmethoddef from_crawler(cls, crawler):return cls(crawler.settings.getlist('USER_AGENTS'))def process_request(self, request, spider):#print "**************************" + random.choice(self.agents)request.headers.setdefault('User-Agent', random.choice(self.agents))class ProxyMiddleware(object):def process_request(self, request, spider):proxy = random.choice(PROXIES)if proxy['user_pass'] is not None:#request.meta['proxy'] = "http://YOUR_PROXY_IP:PORT"request.meta['proxy'] = "http://%s" % proxy['ip_port']#proxy_user_pass = "USERNAME:PASSWORD"encoded_user_pass = base64.encodestring(proxy['user_pass'])request.headers['Proxy-Authorization'] = 'Basic ' + encoded_user_passprint "**************ProxyMiddleware have pass************" + proxy['ip_port']else:print "**************ProxyMiddleware no pass************" + proxy['ip_port']request.meta['proxy'] = "http://%s" % proxy['ip_port']類 RandomUserAgent 主要用來動態獲取 user agent,user agent 列表 USER_AGENTS 在 settings.py 中進行配置。
類 ProxyMiddleware 用來切換代理,proxy 列表 PROXIES 也是在 settings.py 中進行配置。
如果你用的是 socks5 代理,那么對不起,目前 scrapy 還不能直接支持,可以通過 Privoxy 等軟件將其本地轉化為 http 代理
修改 settings.py 配置 USER_AGENTS 和 PROXIES
USER_AGENTS = ["Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)","Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)","Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)","Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)","Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)","Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)","Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)","Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)","Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6","Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1","Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0","Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5","Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11","Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20","Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", ]PROXIES = [{'ip_port': '111.11.228.75:80', 'user_pass': ''},{'ip_port': '120.198.243.22:80', 'user_pass': ''},{'ip_port': '111.8.60.9:8123', 'user_pass': ''},{'ip_port': '101.71.27.120:80', 'user_pass': ''},{'ip_port': '122.96.59.104:80', 'user_pass': ''},{'ip_port': '122.224.249.122:8088', 'user_pass': ''}, ]代理 IP可以網上搜索一下,上面的代理IP獲取自:http://www.xici.net.co/
禁用 cookies:
COOKIES_ENABLED = False設置下載延遲:
DOWNLOAD_DELAY=3設置 DOWNLOADER_MIDDLEWARES?
DOWNLOADER_MIDDLEWARES = {'cnblogs.middlewares.RandomUserAgent': 1,'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,'cnblogs.middlewares.ProxyMiddleware': 100, }保存 settings.py
3、測試
[root@bogon cnblogs]# scrapy crawl CnblogsSpider本文的 user agent 和 proxy 列表都是采用 settings.py 的方式進行設置的,實際生產中 user agent 和 proxy 有可能會經常更新,每次更改配置文件顯得很笨拙也不便于管理。因而,可以根據需要保存在 mysql 數據庫
SSL中間人監測關鍵技術 --- SSL會話劫持
數據流重定向技術是SSL中間人監測的基礎,該技術的使用使得被監測主機與SSL服務器的通信流量都會經過監測主機。對于一般的中間人監測來說,再加上數據轉發機制就已經足夠。但對于SSL中間人監測來說,僅僅通過數據流重定向得到的都是經過加密后的數據,無法直接用來進行HTTP協議解析。故此必需使用SSL會話劫持技術,才能得到被監測主機與SSL服務器之間通信數據的明文。
自SSL問世以來,在其應用范圍越來越廣泛同時,多種針對SSL協議本身的缺陷或者其不規范引用的SSL會話劫持方法也隨之出現,下面將詳細分析兩種典型的SSL會話劫持的實現原理和實現條件。
一、利用偽造的 X.509 證書
1.1 會話劫持原理
當 SSL 客戶端與 SSL 服務端建立連接時,在正常的連接握手階段,客戶端必定會要求服務端出示其X.509公鑰證書,并根據以下3個要素驗證服務器證書的有效性:
- a) 該公鑰證書的subject name(主題名)和所訪問的服務器站點的名稱是否一致;
- b) 該公鑰證書的是否過期;
- c) 該公鑰證書及其簽發者證書鏈中的證書的數字簽名是否有效(層層驗證,一直驗證到根CA證書為止)。
當 SSL 客戶端訪問一個基于 HTTPS 的加密 Web 站點時,只要上述三個要素有一個驗證沒有通過,SSL 協議就會發出告警,大多數瀏覽器會彈出一個提示框,提示服務器證書存在的問題,但不會直接斷開SSL連接,而是讓用戶決定是否繼續。下圖展示了IE 瀏覽器彈出的安全警報提示框。
大多數瀏覽器在驗證到服務器證書存在問題后的處理方式是存在巨大隱患的,因為用戶往往由于缺乏安全意識或者圖方便而選擇接受不安全的證書,這就使得偽造一個和合法證書極為相似的“偽證書”騙取 SSL 客戶端用戶信任的手段成為可能。下圖展示了這種 SSL 會話劫持的主要流程(圖中,C 為 SSL 客戶端,M 為監測主機,S 為 SSL 服務端)
上圖就是基于偽造證書進行劫持的流程,文字描述如下 :主機M通過數據流重定向技術,使得主機C與主機S之間的通信流量都流向主機M,主機C本欲與主機S建立SSL連接,但發送的連接建立請求被重定向到了主機M; 主機C首先與主機M建立TCP連接,然后向主機M發起SSL連接請求; 主機M收到來自主機C的連接請求后,首先與主機S建立TCP連接,然后向主機S發起SSL連接請求; 主機S響應主機M的請求,由此主機M與主機S之間成功建立SSL連接,主機M同時獲得主機S的X.509公鑰證書Certificate_S; 主機M根據Certificate_S中的關鍵信息(主要是subject name、有效期限等)偽造一個極相似的自簽名證書Certificate_S’,并以此證書響應第②步中,來自主機C的SSL連接請求; 主機C的瀏覽器驗證Certificate_S’的有效性,發現subject name與請求的站點名稱一致,證書還在有效期內,但是并非由信任的機構頒發。于是彈出提示框,讓用戶選擇是否繼續。由于Certificate_S’與Certificate_S從外表上幾乎看不出來差別,大部分用戶會選擇繼續( 這是SSL會話劫持可以成功的關鍵 ),由此主機C與主機M成功建立SSL連接。 這樣以后,主機C發往SSL服務端的數據,主機M可以捕獲并解密查看;主機S返回給SSL客戶端的數據,主機M也可以捕獲并解密查看。至此,主機M實現了完整的SSL中間人監測。 經過以上步驟,主機M成功實現了主機C(SSL客戶端)與主機S(SSL服務端)之間的會話劫持,并可以對明文形式的會話內容進行監測。
1.2 成功的必要條件 這種類型的 SSL 會話劫持成功的必要條件如下:
- a) 能夠通過ARP欺騙、DNS欺騙或者瀏覽器數據重定向等欺騙技術,使得SSL客戶端C和服務端S之間的數據都流向中間人監測主機;
- b) SSL客戶端在接收到偽造的X.509證書后,用戶選擇信任該證書,并繼續SSL連接;
- c) SSL服務端未要求對SSL客戶端進行認證。
二、利用 HTTP 與 HTTPS 之間跳轉的驗證漏洞
2.1 會話劫持原理
用戶瀏覽網頁時,使用 SSL 協議的方式一般有兩種。一種是在瀏覽器地址欄輸入網址時直接指定協議類型為HTTPS,另一種是通過HTTP響應的302狀態將網頁重定向到HTTPS 鏈接。2009年2月在美國拉斯維加斯舉行的BlackHat黑客大會上,安全研究人員Moxie Marlinspike 演示了通過自己研發的SSLstrip工具劫持SSL會話來截獲注冊數據的方法,為SSL會話劫持提供了新思路。
SSLstrip 使用了社會工程學的原理:許多人為了圖方便省事,在輸入網址時一般不考慮傳輸協議,習慣上只是簡單輸入主機名,瀏覽器默認在這種情況下會使用 HTTP 協議。例如用戶為了使用Gmail郵箱,直接輸入accounts.google.com,瀏覽器會給谷歌服務器發送一個HTTP 請求,谷歌服務器認為電子郵件屬于應加密的重要事務,使用HTTP不恰當,應改為使用HTTPS,于是它返回一個狀態碼為302的HTTP 響應,給出一個重定向網址https://accounts.google.com/ServiceLogin?passive=1209600&continue=https%3A%2F%2Faccounts.google.com%2FManageAccount&followup=https%3A%2F%2Faccounts.google.com%2FManageAccount,瀏覽器再使用這個重定向網址發出HTTPS?請求。 一個原本應該從頭到尾使用HTTPS加密會話的過程中混入了使用明文傳輸的HTTP會話,一旦HTTP會話被劫持,HTTPS會話就可能受到威脅 。SSLstrip 正是利用這一點,通過劫持HTTP 會話劫持了SSL會話,下圖所示 SSLstrip 原理示意圖。
下面具體闡述基于SSLstrip的SSL會話劫持流程(闡述中依然以主機C為SSL客戶端,主機M為監測主機,主機S為SSL服務端):主機M通過ARP重定向技術,使得主機C所有與外網的通信流都會從主機M處經過。 主機C向主機S的一個HTTPS頁面發出一個HTTP請求,主機M監聽這個請求并轉發給主機S。 主機S返回一個狀態碼為302的HTTP 響應報文,報文消息頭中Location頭域以及消息實體中都給出了重定向網址,形式分別為,“Location:?https://***.com/…”與“”。 主機M解析來自主機S的響應報文,將其中所有的https替換成http,指定主機M另一個未使用的端口為通信端口(假設為8181端口),并且記錄修改過的url。需要做的替換包括:消息頭中的“Location:?https://***.com/…”替換成“Location:?http://***.com:8181/…”;消息實體中鏈接“< a href=”https://***.com/…”>”替換成“”。 主機C解析經過篡改后的HTTP響應報文,經過重定向與主機M的8181端口通過HTTP方式建立了連接,二者之間通信數據明文傳輸。 主機M冒充客戶端與主機S建立HTTPS會話,二者之間的通信數據通過密文傳輸,但主機M可以自由地解密這些數據。 經過以上步驟,主機M成功實現了主機C(SSL客戶端)與主機S(SSL服務端)之間的會話劫持,并可以對明文形式的會話內容進行監測。
2.2 成功的必要條件 這種類型的SSL會話劫持成功的必要條件如下:
- a) 能夠通過ARP欺騙、DNS欺騙或者瀏覽器數據重定向等欺騙技術,使得SSL客戶端和服務端S之間的數據都流向中間人監測主機;
- b) 客戶端訪問的Web頁面存在http頁面至https頁面的跳轉;
- c) SSL服務端未要求對SSL客戶端進行認證。
三、兩種典型 SSL 會話劫持技術的對比小結
傳統的基于偽造 X.509 證書的 SSL 會話劫持方式,其最大的問題就在于客戶端瀏覽器會彈出警告提示對話框,這個提示是如此醒目,以至于只要用戶有一定的安全意識和網絡知識,劫持成功的概率就會大大降低。隨著網絡知識的慢慢普及,這種技術的生存空間會越來越小。
基于 HTTP 與 HTTPS 之間跳轉驗證漏洞的 SSL 會話劫持方式,是近幾年新出的一種技術。在此種方式下,客戶端瀏覽器不會有任何不安全的警告或提示,只是原先的HTTPS連接已經被HTTP連接所替換,迷惑性大大增強。一般為了進一步加強欺騙效果,監測主機還可以一個銀色的“安全鎖”圖案顯示在非安全的網址前面。但其缺陷也很明顯,一旦用戶在瀏覽器地址欄的輸入中指定使用HTTPS協議,就會發現網頁根本無法打開。因此只要用戶養成良好的上網習慣,這種方式的會話劫持就會失敗。
安裝 pycurl
安裝命令:
sudo apt-get install libcurl4-openssl-dev pip install pycurl雜項
scrapy
了解 scrapy 已經做過的功能,優化等。。。防止重復造輪子,如,去重,編碼檢測,dns緩存,http長連接,gzip等等。
JS相關。
這個是被問的最多的。看具體情況解決??赡M相關js執行、繞過,或直接調瀏覽器去訪問。自己用一個JS引擎+模擬一個瀏覽器環境難度太大了(參見V8的DEMO)。
調瀏覽器有很多方法。難以細說,關鍵字如下,selenium,phantomjs,casperjs,ghost,webkit,scrapyjs,splash。一些細節如關掉CSS渲染,圖片加載等。只有scrapyjs是完全異步的,相對是速度最快的,scrapyjs將webkit的事件循環和twisted的事件循環合在一起了。其他的方案要么阻塞,要么用多進程。簡單的js需求(對效率要求不高)隨意選,最優方案是scrapyjs+定制webkit(去掉不需要的功能)。調瀏覽器開頁面是比較耗資源的(主要是cpu)
內容解析。
對于頁面解析最強大的當然是XPATH、css選擇器、正則表達式,這個對于不同網站不同的使用者都不一樣,就不用過多的說明,附兩個比較好的網址:
正則表達式入門:http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html
正則表達式在線測試:http://tool.oschina.net/regex/
其次就是解析庫了,常用的有兩個lxml和BeautifulSoup,對于這兩個的使用介紹兩個比較好的網站:
lxml:http://my.oschina.net/jhao104/blog/639448
BeautifulSoup:http://cuiqingcai.com/1319.html
對于這兩個庫,都是HTML/XML的處理庫,Beautifulsoup純python實現,效率低,但是功能實用,比如能用通過結果搜索獲得某個HTML節點的源碼;lxmlC語言編碼,高效,支持Xpath
機器學習不一定好用(效果問題,人工問題-需要訓練)。還有寫些正則去模糊匹配。
新聞類似的正文提取有readability,boilerplate。
分布式。
首先考慮按任務(目標)切分,然后讓不同目標的爬蟲在不同機器上跑
完全的對等分布式(多爬蟲爬一個目標),把任務隊列替換掉爬蟲改改即可。github里面有幾個現有的實現參考。
分布式需求可能是偽命題。想清楚為何要分布式。硬件夠不夠,像什么拿一個不支持持久化的url隊列的爬蟲說量大需要分布式的,我只能默念,你為何這么吊。
部署,調度
部署推薦scrapyd。這也是官方推薦的方法。
大量爬蟲的調度,這個目前(13-10)沒有現成的合適方法,期望是實現爬蟲的某些配置放數據庫,提供web后臺 ,然后按配置周期、定時運行爬蟲,終止,暫停爬蟲等等??梢詫崿F,但要自己寫不少東西。
ip 限制問題
買的起大量ip的可買(買大量同網段爬可能導致整網段被封)。
找大量免費的開放http代理,篩選可用的,免費開放代理不可靠,寫個調度機制,自動根據成功次數,延遲等選擇合適代理,這個功能難以在scrapy內實現,參考scrapinghub的crawlera,我完成了一個本地版。
在開發爬蟲過程中經常會遇到IP被封掉的情況,這時就需要用到代理IP;
在urllib2包中有ProxyHandler類,通過此類可以設置代理訪問網頁,如下代碼片段:
import urllib2proxy = urllib2.ProxyHandler({'http': '127.0.0.1:8087'})opener = urllib2.build_opener(proxy)urllib2.install_opener(opener)response = urllib2.urlopen('http://www.baidu.com')print response.read()url 去重
如果有千萬級的URL需要去重,需要仔細看下scrapy的去重機制和bloom filter(布隆過濾器)。bloomfilter有個公式可以算需要多少內存。另bloomfilter + scrapy在github有現有實現可以參考。
存儲。
mongodb,mongodb不滿足某些功能時考慮hbase,參考http://blog.scrapinghub.com/2013/05/13/mongo-bad-for-scraped-data/
硬件
硬件扛不住別玩爬蟲。。。曾在I3 4G 1T上跑爬蟲??ㄔ诖疟Pio(量大,磁盤io差,內存低),出現內存占用飆升。很難調試(調試爬蟲看實際跑耗時較長),初步以為是爬蟲有問題內存占用高導致數據庫卡。調試結果確認為,配置低量太大,導致數據庫慢,數據庫慢之后爬蟲任務隊列占滿內存并開始寫磁盤,又循環導致數據庫慢。
爬蟲監控
scrapyd自帶簡單的監控,不夠的話用scrapy的webservice自己寫
如何防止死循環
在 Scrapy 的默認配置中,是根據 url 進行去重的。這個對付一般網站是夠的。但是有一些網站的 SEO 做的很變態:為了讓爬蟲多抓,會根據 request,動態的生成一些鏈接,導致爬蟲 在網站上抓取大量的隨機頁面,甚至是死循環。。
為了解決這個問題,有2個方案:
(1) 在 setting.py 中,設定爬蟲的嵌套次數上限(全局設定,實際是通過 DepthMiddleware 實現的):
DEPTH_LIMIT = 20(2) 在 parse 中通過讀取 response 來自行判斷( spider級別設定 ) :
def parse(self, response):if response.meta['depth'] > 100:print 'Loop?'學習爬蟲的正確打開方式
看了大部分回答不禁嘆口氣,主要是因為看到很多大牛在回答像? "如何入門爬蟲"? 這種問題的時候,一如當年學霸講解題目,跳步無數,然后留下一句? "?不就是這樣推嘛 ",讓一眾小白菜鳥一臉懵逼。。作為一個0起步(之前連python都不會),目前總算掌握基礎,開始向上進階的菜鳥,深知其中的不易,所以我會在這個回答里,盡可能全面、細節地分享給大家從0學習爬蟲的各種步驟,如果對你有幫助,請點贊~
首先!你要對爬蟲有個明確的認識。。。
在戰略上藐視:
“所有網站皆可爬”:互聯網的內容都是人寫出來的,而且都是偷懶寫出來的(不會第一頁是a,下一頁是8),所以肯定有規律,這就給人有了爬取的可能,可以說,天下沒有不能爬的網站 “框架不變”:網站不同,但是原理都類似,大部分爬蟲都是從 發送請求——獲得頁面——解析頁面——下載內容——儲存內容 這樣的流程來進行,只是用的工具不同
在戰術上重視:
持之以恒,戒驕戒躁:對于初學入門,不可輕易自滿,以為爬了一點內容就什么都會爬了,爬蟲雖然是比較簡單的技術,但是往深學也是沒有止境的(比如搜索引擎等)!只有不斷嘗試,刻苦鉆研才是王道!(為何有種小學作文即視感)
然后,你需要一個宏偉的目標,來讓你有持續學習的動力(沒有實操項目,真的很難有動力)
我要爬整個豆瓣!... 我要爬整個草什么榴社區!我要爬知乎各種妹子的聯系方式*&^#%^$#
接著,你需要捫心自問一下,自己的 python 基本功吼不吼啊? 吼啊!——OK,開始歡快地學習爬蟲吧 !
不吼?你還需要學習一個!趕緊回去看Python核心編程教程。至少這些功能和語法你要有基本的掌握 : list,dict:用來序列化你爬的東西 切片:用來對爬取的內容進行分割,生成 條件判斷(if等):用來解決爬蟲過程中哪些要哪些不要的問題 循環和迭代(for while ):用來循環,重復爬蟲動作 文件讀寫操作:用來讀取參數、保存爬下來的內容等
然后,你需要補充一下下面幾個內容,作為你的知識儲備:
(注:這里并非要求“掌握”,下面講的兩點,只需要先了解,然后通過具體項目來不斷實踐,直到熟練掌握)
1、網頁的基本知識:
基本的HTML語言知識(知道href等大學計算機一級內容即可)
理解網站的發包和收包的概念(POST GET)
稍微一點點的js知識,用于理解動態網頁(當然如果本身就懂當然更好啦)
2、一些分析語言,為接下來解析網頁內容做準備
NO.1 正則表達式:扛把子技術,總得會最基礎的:
NO.2 XPATH:高效的分析語言,表達清晰簡單,掌握了以后基本可以不用正則
參考:XPath 教程[http://link.zhihu.com/?target=http%3A//www.w3school.com.cn/xpath/]
NO.3 Beautifulsoup:
美麗湯模塊解析網頁神器,一款神器,如果不用一些爬蟲框架(如后文講到的scrapy),配合request,urllib等模塊(后面會詳細講),可以編寫各種小巧精干的爬蟲腳本
官網文檔:Beautiful Soup 4.2.0 文檔[http://link.zhihu.com/?target=http%3A//beautifulsoup.readthedocs.org/zh_CN/latest/]
參考案例:
接著,你需要一些高效的工具來輔助
(同樣,這里先了解,到具體的項目的時候,再熟悉運用)
NO.1 F12 開發者工具:
看源代碼:快速定位元素
分析xpath:1、此處建議谷歌系瀏覽器,可以在源碼界面直接右鍵看
NO.2 抓包工具:
推薦httpfox,火狐瀏覽器下的插件,比谷歌火狐系自帶的F12工具都要好,可以方便查看網站收包發包的信息
NO.3 XPATH CHECKER (火狐插件):
非常不錯的 xpath 測試工具,但是有幾個坑,都是個人踩過的,,在此告誡大家:
- 1、xpath checker 生成的是絕對路徑,遇到一些動態生成的圖標(常見的有列表翻頁按鈕等),飄忽不定的絕對路徑很有可能造成錯誤,所以這里建議在真正分析的時候,只是作為參考
- 2、記得把如下圖 xpath 框里的“x:”去掉,貌似這個是早期版本xpath的語法,目前已經和一些模塊不兼容(比如scrapy),還是刪去避免報錯
NO.4 正則表達測試工具:
在線正則表達式測試(http://link.zhihu.com/?target=http%3A//tool.oschina.net/regex/) ,拿來多練練手,也輔助分析!里面有很多現成的正則表達式可以用,也可以進行參考!
ok!這些你都基本有一些了解了,現在開始進入抓取時間,上各種模塊吧!python 的火,很大原因就是各種好用的模塊,這些模塊是居家旅行爬網站常備的——
urllib urllib2 requests**不想重復造輪子,有沒有現成的框架? 華麗麗的scrapy(這塊我會重點講,我的最愛)**
遇到動態頁面怎么辦?
selenium,會了這個配合 scrapy 無往不利,是居家旅行爬網站又一神器,
爬來的東西怎么用?
pandas(基于 numpy 的數據分析模塊,相信我,如果你不是專門搞 TB 級數據的,這個就夠了)
然后是數據庫,這里我認為開始并不需要非常深入,在需要的時候再學習即可
mysql mongodb sqllite遇到反爬蟲策略驗證碼之類咋整?
PIL opencv pybrain進階技術
多線程、分布式
Python 網頁爬蟲 & 文本處理 & 科學計算 & 機器學習 & 數據挖掘兵器譜
曾經因為NLTK的緣故開始學習Python,之后漸漸成為我工作中的第一輔助腳本語言,雖然開發語言是C/C++,但平時的很多文本數據處理任務都交給了Python。離開騰訊創業后,第一個作品課程圖譜也是選擇了Python系的Flask框架,漸漸的將自己的絕大部分工作交給了Python。這些年來,接觸和使用了很多Python工具包,特別是在文本處理,科學計算,機器學習和數據挖掘領域,有很多很多優秀的Python工具包可供使用,所以作為Pythoner,也是相當幸福的。其實如果仔細留意微博,你會發現很多這方面的分享,自己也Google了一下,發現也有同學總結了“Python機器學習庫”,不過總感覺缺少點什么。最近流行一個詞,全棧工程師(full stack engineer),作為一個苦逼的創業者,天然的要把自己打造成一個full stack engineer,而這個過程中,這些Python工具包給自己提供了足夠的火力,所以想起了這個系列。當然,這也僅僅是拋磚引玉,希望大家能提供更多的線索,來匯總整理一套Python網頁爬蟲,文本處理,科學計算,機器學習和數據挖掘的兵器譜。
一、Python網頁爬蟲工具集
一個真實的項目,一定是從獲取數據開始的。無論文本處理,機器學習和數據挖掘,都需要數據,除了通過一些渠道購買或者下載的專業數據外,常常需要大家自己動手爬數據,這個時候,爬蟲就顯得格外重要了,幸好,Python提供了一批很不錯的網頁爬蟲工具框架,既能爬取數據,也能獲取和清洗數據,我們也就從這里開始了:
1.?Scrapy
Scrapy, a fast high-level screen scraping and web crawling framework for Python.
鼎鼎大名的Scrapy,相信不少同學都有耳聞,課程圖譜中的很多課程都是依靠Scrapy抓去的,這方面的介紹文章有很多,推薦大牛pluskid早年的一篇文章:《Scrapy 輕松定制網絡爬蟲》,歷久彌新。
官方主頁:http://scrapy.org/
Github代碼頁:?https://github.com/scrapy/scrapy
2.?Beautiful Soup
You didn’t write that awful page. You’re just trying to get some data out of it. Beautiful Soup is here to help. Since 2004, it’s been saving programmers hours or days of work on quick-turnaround screen scraping projects.
讀書的時候通過《集體智慧編程》這本書知道Beautiful Soup的,后來也偶爾會用用,非常棒的一套工具??陀^的說,Beautifu Soup不完全是一套爬蟲工具,需要配合urllib使用,而是一套HTML/XML數據分析,清洗和獲取工具。
官方主頁:http://www.crummy.com/software/BeautifulSoup/
3.?Python-Goose
Html Content / Article Extractor, web scrapping lib in Python
Goose最早是用Java寫得,后來用Scala重寫,是一個Scala項目。Python-Goose用Python重寫,依賴了Beautiful Soup。前段時間用過,感覺很不錯,給定一個文章的URL, 獲取文章的標題和內容很方便。
Github主頁:https://github.com/grangier/python-goose
二、Python文本處理工具集
從網頁上獲取文本數據之后,依據任務的不同,就需要進行基本的文本處理了,譬如對于英文來說,需要基本的tokenize,對于中文,則需要常見的中文分詞,進一步的話,無論英文中文,還可以詞性標注,句法分析,關鍵詞提取,文本分類,情感分析等等。這個方面,特別是面向英文領域,有很多優秀的工具包,我們一一道來。
1.?NLTK?— Natural Language Toolkit
NLTK is a leading platform for building Python programs to work with human language data. It provides easy-to-use interfaces to over 50 corpora and lexical resources such as WordNet, along with a suite of text processing libraries for classification, tokenization, stemming, tagging, parsing, and semantic reasoning, and an active discussion forum.
搞自然語言處理的同學應該沒有人不知道NLTK吧,這里也就不多說了。不過推薦兩本書籍給剛剛接觸NLTK或者需要詳細了解NLTK的同學: 一個是官方的《Natural Language Processing with Python》,以介紹NLTK里的功能用法為主,同時附帶一些Python知識,同時國內陳濤同學友情翻譯了一個中文版,這里可以看到:推薦《用Python進行自然語言處理》中文翻譯-NLTK配套書;另外一本是《Python Text Processing with NLTK 2.0 Cookbook》,這本書要深入一些,會涉及到NLTK的代碼結構,同時會介紹如何定制自己的語料和模型等,相當不錯。
官方主頁:http://www.nltk.org/
Github代碼頁:https://github.com/nltk/nltk
2.?Pattern
Pattern is a web mining module for the Python programming language.
It has tools for data mining (Google, Twitter and Wikipedia API, a web crawler, a HTML DOM parser), natural language processing (part-of-speech taggers, n-gram search, sentiment analysis, WordNet), machine learning (vector space model, clustering, SVM), network analysis and canvas visualization.
Pattern由比利時安特衛普大學CLiPS實驗室出品,客觀的說,Pattern不僅僅是一套文本處理工具,它更是一套web數據挖掘工具,囊括了數據抓取模塊(包括Google, Twitter, 維基百科的API,以及爬蟲和HTML分析器),文本處理模塊(詞性標注,情感分析等),機器學習模塊(VSM, 聚類,SVM)以及可視化模塊等,可以說,Pattern的這一整套邏輯也是這篇文章的組織邏輯,不過這里我們暫且把Pattern放到文本處理部分。我個人主要使用的是它的英文處理模塊Pattern.en, 有很多很不錯的文本處理功能,包括基礎的tokenize, 詞性標注,句子切分,語法檢查,拼寫糾錯,情感分析,句法分析等,相當不錯。
官方主頁:http://www.clips.ua.ac.be/pattern
3.?TextBlob: Simplified Text Processing
TextBlob is a Python (2 and 3) library for processing textual data. It provides a simple API for diving into common natural language processing (NLP) tasks such as part-of-speech tagging, noun phrase extraction, sentiment analysis, classification, translation, and more.
TextBlob是一個很有意思的Python文本處理工具包,它其實是基于上面兩個Python工具包NLKT和Pattern做了封裝(TextBlob stands on the giant shoulders of NLTK and pattern, and plays nicely with both),同時提供了很多文本處理功能的接口,包括詞性標注,名詞短語提取,情感分析,文本分類,拼寫檢查等,甚至包括翻譯和語言檢測,不過這個是基于Google的API的,有調用次數限制。TextBlob相對比較年輕,有興趣的同學可以關注。
官方主頁:http://textblob.readthedocs.org/en/dev/
Github代碼頁:https://github.com/sloria/textblob
4.?MBSP?for Python
MBSP is a text analysis system based on the TiMBL and MBT memory based learning applications developed at CLiPS and ILK. It provides tools for Tokenization and Sentence Splitting, Part of Speech Tagging, Chunking, Lemmatization, Relation Finding and Prepositional Phrase Attachment.
MBSP與Pattern同源,同出自比利時安特衛普大學CLiPS實驗室,提供了Word Tokenization, 句子切分,詞性標注,Chunking, Lemmatization,句法分析等基本的文本處理功能,感興趣的同學可以關注。
官方主頁:http://www.clips.ua.ac.be/pages/MBSP
5.?Gensim: Topic modeling for humans
Gensim是一個相當專業的主題模型Python工具包,無論是代碼還是文檔,我們曾經用《如何計算兩個文檔的相似度》介紹過Gensim的安裝和使用過程,這里就不多說了。
官方主頁:http://radimrehurek.com/gensim/index.html
github代碼頁:https://github.com/piskvorky/gensim
6.?langid.py: Stand-alone language identification system
語言檢測是一個很有意思的話題,不過相對比較成熟,這方面的解決方案很多,也有很多不錯的開源工具包,不過對于Python來說,我使用過langid這個工具包,也非常愿意推薦它。langid目前支持97種語言的檢測,提供了很多易用的功能,包括可以啟動一個建議的server,通過json調用其API,可定制訓練自己的語言檢測模型等,可以說是“麻雀雖小,五臟俱全”。
Github主頁:https://github.com/saffsd/langid.py
7.?Jieba: 結巴中文分詞
“結巴”中文分詞:做最好的Python中文分詞組件 “Jieba” (Chinese for “to stutter”) Chinese text segmentation: built to be the best Python Chinese word segmentation module.
好了,終于可以說一個國內的Python文本處理工具包了:結巴分詞,其功能包括支持三種分詞模式(精確模式、全模式、搜索引擎模式),支持繁體分詞,支持自定義詞典等,是目前一個非常不錯的Python中文分詞解決方案。
Github主頁:https://github.com/fxsjy/jieba
8.?xTAS
xtas, the eXtensible Text Analysis Suite, a distributed text analysis package based on Celery and Elasticsearch.
感謝微博朋友?@大山坡的春?提供的線索:我們組同事之前發布了xTAS,也是基于python的text mining工具包,歡迎使用,鏈接:http://t.cn/RPbEZOW。看起來很不錯的樣子,回頭試用一下。
Github代碼頁:https://github.com/NLeSC/xtas
三、Python 科學計算工具包
說起科學計算,大家首先想起的是 Matlab,集數值計算,可視化工具及交互于一身,不過可惜是一個商業產品。開源方面除了GNU Octave在嘗試做一個類似Matlab的工具包外,Python的這幾個工具包集合到一起也可以替代Matlab的相應功能:NumPy+SciPy+Matplotlib+iPython。同時,這幾個工具包,特別是NumPy和SciPy,也是很多Python文本處理 & 機器學習 & 數據挖掘工具包的基礎,非常重要。最后再推薦一個系列《用Python做科學計算》,將會涉及到NumPy, SciPy, Matplotlib,可以做參考。
1.?NumPy
NumPy is the fundamental package for scientific computing with Python. It contains among other things:
1)a powerful N-dimensional array object
2)sophisticated (broadcasting) functions
3)tools for integrating C/C++ and Fortran code
4) useful linear algebra, Fourier transform, and random number capabilities
Besides its obvious scientific uses, NumPy can also be used as an efficient multi-dimensional container of generic data. Arbitrary data-types can be defined. This allows NumPy to seamlessly and speedily integrate with a wide variety of databases.
NumPy幾乎是一個無法回避的科學計算工具包,最常用的也許是它的N維數組對象,其他還包括一些成熟的函數庫,用于整合C/C++和Fortran代碼的工具包,線性代數、傅里葉變換和隨機數生成函數等。NumPy提供了兩種基本的對象:ndarray(N-dimensional array object)和 ufunc(universal function object)。ndarray是存儲單一數據類型的多維數組,而ufunc則是能夠對數組進行處理的函數。
官方主頁:http://www.numpy.org/
2.?SciPy:Scientific Computing Tools for Python
SciPy refers to several related but distinct entities:
1)The SciPy Stack, a collection of open source software for scientific computing in Python, and particularly a specified set of core packages.
2)The community of people who use and develop this stack.
3)Several conferences dedicated to scientific computing in Python – SciPy, EuroSciPy and SciPy.in.
4)The SciPy library, one component of the SciPy stack, providing many numerical routines.
“SciPy是一個開源的Python算法庫和數學工具包,SciPy包含的模塊有最優化、線性代數、積分、插值、特殊函數、快速傅里葉變換、信號處理和圖像處理、常微分方程求解和其他科學與工程中常用的計算。其功能與軟件MATLAB、Scilab和GNU Octave類似。 Numpy和Scipy常常結合著使用,Python大多數機器學習庫都依賴于這兩個模塊?!薄?引用自“Python機器學習庫”
官方主頁:http://www.scipy.org/
3.?Matplotlib
matplotlib is a python 2D plotting library which produces publication quality figures in a variety of hardcopy formats and interactive environments across platforms. matplotlib can be used in python scripts, the python and ipython shell (ala MATLAB?* or Mathematica??), web application servers, and six graphical user interface toolkits.
matplotlib 是python最著名的繪圖庫,它提供了一整套和matlab相似的命令API,十分適合交互式地進行制圖。而且也可以方便地將它作為繪圖控件,嵌入GUI應用程序中。Matplotlib可以配合ipython shell使用,提供不亞于Matlab的繪圖體驗,總之用過了都說好。
官方主頁:http://matplotlib.org/
4.?iPython
IPython provides a rich architecture for interactive computing with:
1)Powerful interactive shells (terminal and Qt-based).
2)A browser-based notebook with support for code, text, mathematical expressions, inline plots and other rich media.
3)Support for interactive data visualization and use of GUI toolkits.
4)Flexible, embeddable interpreters to load into your own projects.
5)Easy to use, high performance tools for parallel computing.
“iPython 是一個Python 的交互式Shell,比默認的Python Shell 好用得多,功能也更強大。 她支持語法高亮、自動完成、代碼調試、對象自省,支持 Bash Shell 命令,內置了許多很有用的功能和函式等,非常容易使用。 ” 啟動iPython的時候用這個命令“ipython –pylab”,默認開啟了matploblib的繪圖交互,用起來很方便。
官方主頁:http://ipython.org/
四、Python 機器學習 & 數據挖掘 工具包
機器學習和數據挖掘這兩個概念不太好區分,這里就放到一起了。這方面的開源Python工具包有很多,這里先從熟悉的講起,再補充其他來源的資料,也歡迎大家補充。
1.?scikit-learn: Machine Learning in Python
scikit-learn (formerly scikits.learn) is an open source machine learning library for the Python programming language. It features various classification, regression and clustering algorithms including support vector machines, logistic regression, naive Bayes, random forests, gradient boosting, k-means and DBSCAN, and is designed to interoperate with the Python numerical and scientific libraries NumPy and SciPy.
首先推薦大名鼎鼎的scikit-learn,scikit-learn是一個基于NumPy, SciPy, Matplotlib的開源機器學習工具包,主要涵蓋分類,回歸和聚類算法,例如SVM, 邏輯回歸,樸素貝葉斯,隨機森林,k-means等算法,代碼和文檔都非常不錯,在許多Python項目中都有應用。例如在我們熟悉的NLTK中,分類器方面就有專門針對scikit-learn的接口,可以調用scikit-learn的分類算法以及訓練數據來訓練分類器模型。這里推薦一個視頻,也是我早期遇到scikit-learn的時候推薦過的:推薦一個Python機器學習工具包Scikit-learn以及相關視頻–Tutorial: scikit-learn – Machine Learning in Python
官方主頁:http://scikit-learn.org/
2.?Pandas: Python Data Analysis Library
Pandas is a software library written for the Python programming language for data manipulation and analysis. In particular, it offers data structures and operations for manipulating numerical tables and time series.
第一次接觸Pandas是由于Udacity上的一門數據分析課程“Introduction to Data Science” 的Project需要用Pandas庫,所以學習了一下Pandas。Pandas也是基于NumPy和Matplotlib開發的,主要用于數據分析和數據可視化,它的數據結構DataFrame和R語言里的data.frame很像,特別是對于時間序列數據有自己的一套分析機制,非常不錯。這里推薦一本書《Python for Data Analysis》,作者是Pandas的主力開發,依次介紹了iPython, NumPy, Pandas里的相關功能,數據可視化,數據清洗和加工,時間數據處理等,案例包括金融股票數據挖掘等,相當不錯。
官方主頁:http://pandas.pydata.org/
=====================================================================
分割線,以上工具包基本上都是自己用過的,以下來源于其他同學的線索,特別是《Python機器學習庫》,《23個python的機器學習包》,做了一點增刪修改,歡迎大家補充
=====================================================================
3.?mlpy – Machine Learning Python
mlpy is a Python module for Machine Learning built on top of NumPy/SciPy and the GNU Scientific Libraries.
mlpy provides a wide range of state-of-the-art machine learning methods for supervised and unsupervised problems and it is aimed at finding a reasonable compromise among modularity, maintainability, reproducibility, usability and efficiency. mlpy is multiplatform, it works with Python 2 and 3 and it is Open Source, distributed under the GNU General Public License version 3.
官方主頁:http://mlpy.sourceforge.net/
4.?MDP:The Modular toolkit for Data Processing
Modular toolkit for Data Processing (MDP) is a Python data processing framework.
From the user’s perspective, MDP is a collection of supervised and unsupervised learning algorithms and other data processing units that can be combined into data processing sequences and more complex feed-forward network architectures.
From the scientific developer’s perspective, MDP is a modular framework, which can easily be expanded. The implementation of new algorithms is easy and intuitive. The new implemented units are then automatically integrated with the rest of the library.
The base of available algorithms is steadily increasing and includes signal processing methods (Principal Component Analysis, Independent Component Analysis, Slow Feature Analysis), manifold learning methods ([Hessian] Locally Linear Embedding), several classifiers, probabilistic methods (Factor Analysis, RBM), data pre-processing methods, and many others.
“MDP用于數據處理的模塊化工具包,一個Python數據處理框架。 從用戶的觀點,MDP是能夠被整合到數據處理序列和更復雜的前饋網絡結構的一批監督學習和非監督學習算法和其他數據處理單元。計算依照速度和內存需求而高效的執行。從科學開發者的觀點,MDP是一個模塊框架,它能夠被容易地擴展。新算法的實現是容易且直觀的。新實現的單元然后被自動地與程序庫的其余部件進行整合。MDP在神經科學的理論研究背景下被編寫,但是它已經被設計為在使用可訓練數據處理算法的任何情況中都是有用的。其站在用戶一邊的簡單性,各種不同的隨時可用的算法,及應用單元的可重用性,使得它也是一個有用的教學工具?!?/p>
官方主頁:http://mdp-toolkit.sourceforge.net/
5.?PyBrain
PyBrain is a modular Machine Learning Library for Python. Its goal is to offer flexible, easy-to-use yet still powerful algorithms for Machine Learning Tasks and a variety of predefined environments to test and compare your algorithms.
PyBrain is short for Python-Based Reinforcement Learning, Artificial Intelligence and Neural Network Library. In fact, we came up with the name first and later reverse-engineered this quite descriptive “Backronym”.
“PyBrain(Python-Based Reinforcement Learning, Artificial Intelligence and Neural Network)是Python的一個機器學習模塊,它的目標是為機器學習任務提供靈活、易應、強大的機器學習算法。(這名字很霸氣)
PyBrain正如其名,包括神經網絡、強化學習(及二者結合)、無監督學習、進化算法。因為目前的許多問題需要處理連續態和行為空間,必須使用函數逼近(如神經網絡)以應對高維數據。PyBrain以神經網絡為核心,所有的訓練方法都以神經網絡為一個實例。”
官方主頁:http://www.pybrain.org/
6.?PyML?– machine learning in Python
PyML is an interactive object oriented framework for machine learning written in Python. PyML focuses on SVMs and other kernel methods. It is supported on Linux and Mac OS X.
“PyML是一個Python機器學習工具包,為各分類和回歸方法提供靈活的架構。它主要提供特征選擇、模型選擇、組合分類器、分類評估等功能?!?/p>
項目主頁:http://pyml.sourceforge.net/
7.?Milk:Machine learning toolkit in Python.
Its focus is on supervised classification with several classifiers available:
SVMs (based on libsvm), k-NN, random forests, decision trees. It also performs
feature selection. These classifiers can be combined in many ways to form
different classification systems.
“Milk是Python的一個機器學習工具箱,其重點是提供監督分類法與幾種有效的分類分析:SVMs(基于libsvm),K-NN,隨機森林經濟和決策樹。它還可以進行特征選擇。這些分類可以在許多方面相結合,形成不同的分類系統。對于無監督學習,它提供K-means和affinity propagation聚類算法。”
官方主頁:http://luispedro.org/software/milk
http://luispedro.org/software/milk
8.?PyMVPA: MultiVariate Pattern Analysis (MVPA) in Python
PyMVPA is a Python package intended to ease statistical learning analyses of large datasets. It offers an extensible framework with a high-level interface to a broad range of algorithms for classification, regression, feature selection, data import and export. It is designed to integrate well with related software packages, such as scikit-learn, and MDP. While it is not limited to the neuroimaging domain, it is eminently suited for such datasets. PyMVPA is free software and requires nothing but free-software to run.
“PyMVPA(Multivariate Pattern Analysis in Python)是為大數據集提供統計學習分析的Python工具包,它提供了一個靈活可擴展的框架。它提供的功能有分類、回歸、特征選擇、數據導入導出、可視化等”
官方主頁:http://www.pymvpa.org/
9.?Pyrallel?– Parallel Data Analytics in Python
Experimental project to investigate distributed computation patterns for machine learning and other semi-interactive data analytics tasks.
“Pyrallel(Parallel Data Analytics in Python)基于分布式計算模式的機器學習和半交互式的試驗項目,可在小型集群上運行”
Github代碼頁:http://github.com/pydata/pyrallel
10.?Monte?– gradient based learning in Python
Monte (python) is a Python framework for building gradient based learning machines, like neural networks, conditional random fields, logistic regression, etc. Monte contains modules (that hold parameters, a cost-function and a gradient-function) and trainers (that can adapt a module’s parameters by minimizing its cost-function on training data).
Modules are usually composed of other modules, which can in turn contain other modules, etc. Gradients of decomposable systems like these can be computed with back-propagation.
“Monte (machine learning in pure Python)是一個純Python機器學習庫。它可以迅速構建神經網絡、條件隨機場、邏輯回歸等模型,使用inline-C優化,極易使用和擴展?!?/p>
官方主頁:http://montepython.sourceforge.net
11.?Theano
Theano is a Python library that allows you to define, optimize, and evaluate mathematical expressions involving multi-dimensional arrays efficiently. Theano features:
1)tight integration with NumPy – Use numpy.ndarray in Theano-compiled functions.
2)transparent use of a GPU – Perform data-intensive calculations up to 140x faster than with CPU.(float32 only)
3)efficient symbolic differentiation – Theano does your derivatives for function with one or many inputs.
4)speed and stability optimizations – Get the right answer for log(1+x) even when x is really tiny.
5)dynamic C code generation – Evaluate expressions faster.
6) extensive unit-testing and self-verification – Detect and diagnose many types of mistake.
Theano has been powering large-scale computationally intensive scientific investigations since 2007. But it is also approachable enough to be used in the classroom (IFT6266 at the University of Montreal).
“Theano 是一個 Python 庫,用來定義、優化和模擬數學表達式計算,用于高效的解決多維數組的計算問題。Theano的特點:緊密集成Numpy;高效的數據密集型GPU計算;高效的符號微分運算;高速和穩定的優化;動態生成c代碼;廣泛的單元測試和自我驗證。自2007年以來,Theano已被廣泛應用于科學運算。theano使得構建深度學習模型更加容易,可以快速實現多種模型。PS:Theano,一位希臘美女,Croton最有權勢的Milo的女兒,后來成為了畢達哥拉斯的老婆。”
12.?Pylearn2
Pylearn2 is a machine learning library. Most of its functionality is built on top of Theano. This means you can write Pylearn2 plugins (new models, algorithms, etc) using mathematical expressions, and theano will optimize and stabilize those expressions for you, and compile them to a backend of your choice (CPU or GPU).
“Pylearn2建立在theano上,部分依賴scikit-learn上,目前Pylearn2正處于開發中,將可以處理向量、圖像、視頻等數據,提供MLP、RBM、SDA等深度學習模型。”
總結
以上是生活随笔為你收集整理的爬虫教程( 6 ) --- 爬虫 进阶、扩展的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue使用trim修饰符去掉空格
- 下一篇: 产品文档概述