多線程爬蟲
有些時候,比如下載圖片,因為下載圖片是一個耗時的操作。如果采用之前那種同步的方式下載。那效率肯會特別慢。這時候我們就可以考慮使用多線程的方式來下載圖片。
Queue(隊列對象) Queue是python中的標(biāo)準(zhǔn)庫,可以直接import Queue引用;
隊列是線程間最常用的交換數(shù)據(jù)的形式
對于資源,加鎖是個重要的環(huán)節(jié)。因為python原生的list,dict等,都是not thread safe的。而Queue,是線程安全的,因此在滿足使用條件下,建議使用隊列
包中常用的方法
- Queue.qsize() 返回隊列的大小
- Queue.empty() 如果隊列為空,返回True,反之False
- Queue.full() 如果隊列滿了,返回True,反之False
- Queue.full 與 maxsize 大小對應(yīng)
- Queue.get([block[, timeout]])獲取隊列,timeout等待時間
迭代器
迭代是訪問集合元素的一種方式。迭代器是一個可以記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結(jié)束。迭代器只能往前不會后退。
可迭代對象 我們已經(jīng)知道可以對list、tuple、str等類型的數(shù)據(jù)使用for...in...的循環(huán)語法從其中依次拿到數(shù)據(jù)進(jìn)行使用,我們把這樣的過程稱為遍歷,也叫迭代
可迭代對象的本質(zhì) 可迭代對象進(jìn)行迭代使用的過程,每迭代一次(即在for...in...中每循環(huán)一次)都會返回對象中的下一條數(shù)據(jù),一直向后讀取數(shù)據(jù)直到迭代了所有數(shù)據(jù)后結(jié)束。
可迭代對象通過iter方法向我們提供一個迭代器,我們在迭代一個可迭代對象的時候,實際上就是先獲取該對象提供的一個迭代器,然后通過這個迭代器來依次獲取對象中的每一個數(shù)據(jù).
- 一個具備了 iter 方法的對象,就是一個 可迭代對象
iter()函數(shù)與next()函數(shù)
list、tuple等都是可迭代對象,我們可以通過iter()函數(shù)獲取這些可迭代對象的迭代器。然后我們可以對獲取到的迭代器不斷使用next()函數(shù)來獲取下一條數(shù)據(jù)。iter()函數(shù)實際上就是調(diào)用了可迭代對象的iter方法。
-
迭代器Iterator
迭代器是用來幫助我們記錄每次迭代訪問到的位置,當(dāng)我們對迭代器使用next()函數(shù)的時候,迭代器會向我們返回它所記錄位置的下一個位置的數(shù)據(jù)。實際上,在使用next()函數(shù)的時候,調(diào)用的就是迭代器對象的next方法。所以,我們要想構(gòu)造一個迭代器,就要實現(xiàn)它的next方法。并且python要求迭代器本身也是可迭代的,所以我們還要為迭代器實現(xiàn)iter方法,迭代器的iter方法返回自身即可。一個實現(xiàn)了iter方法和next方法的對象,就是迭代器。
for...in...循環(huán)的本質(zhì)
for item in Iterable 循環(huán)的本質(zhì)就是先通過 iter()函數(shù) 獲取 可迭代對象Iterable的迭代器,然后對獲取到的迭代器不斷調(diào)用 next() 方法來獲取下一個值并將其賦值給item,當(dāng)遇到 StopIteration 的異常后循環(huán)結(jié)束。
生成器
- 生成器 利用迭代器,我們可以在每次迭代獲取數(shù)據(jù)(通過next()方法)時按照特定的規(guī)律進(jìn)行生成。但是我們在實現(xiàn)一個迭代器時,關(guān)于當(dāng)前迭代到的狀態(tài)需要我們自己記錄,進(jìn)而才能根據(jù)當(dāng)前狀態(tài)生成下一個數(shù)據(jù)。為了達(dá)到記錄當(dāng)前狀態(tài),并配合next()函數(shù)進(jìn)行迭代使用,我們可以采用更簡便的語法,即生成器(generator)。生成器是一類特殊的迭代器。
- 使用了yield關(guān)鍵字的函數(shù)不再是函數(shù),而是生成器。(使用了yield的函數(shù)就是生成器)
- yield關(guān)鍵字有兩點作用:
- 保存當(dāng)前運行狀態(tài)(斷點),然后暫停執(zhí)行,即將生成器(函數(shù))掛起
- 將yield關(guān)鍵字后面表達(dá)式的值作為返回值返回,此時可以理解為起到了return的作用
- 可以使用next()函數(shù)讓生成器從斷點處繼續(xù)執(zhí)行.
協(xié)程
協(xié)程,英文叫做 Coroutine,又稱微線程,纖程,協(xié)程是一種用戶態(tài)的輕量級線程。
協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復(fù)先前保存的寄存器上下文和棧。因此協(xié)程能保留上一次調(diào)用時的狀態(tài),即所有局部狀態(tài)的一個特定組合,每次過程重入時,就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài)。
協(xié)程本質(zhì)上是個單進(jìn)程,協(xié)程相對于多進(jìn)程來說,無需線程上下文切換的開銷,無需原子操作鎖定及同步的開銷,編程模型也非常簡單。
我們可以使用協(xié)程來實現(xiàn)異步操作,比如在網(wǎng)絡(luò)爬蟲場景下,我們發(fā)出一個請求之后,需要等待一定的時間才能得到響應(yīng),但其實在這個等待過程中,程序可以干許多其他的事情,等到響應(yīng)得到之后才切換回來繼續(xù)處理,這樣可以充分利用 CPU 和其他資源,這就是異步協(xié)程的優(yōu)勢。
- 協(xié)程和線程差異
在實現(xiàn)多任務(wù)時, 線程切換從系統(tǒng)層面遠(yuǎn)不止保存和恢復(fù) CPU上下文這么簡單。 操作系統(tǒng)為了程序運行的高效性每個線程都有自己緩存Cache等等數(shù)據(jù),操作系統(tǒng)還會幫你做這些數(shù)據(jù)的恢復(fù)操作。 所以線程的切換非常耗性能。但是協(xié)程的切換只是單純的操作CPU的上下文,所以一秒鐘切換個上百萬次系統(tǒng)都抗的住
greenlet
為了更好使用協(xié)程來完成多任務(wù),python中的greenlet模塊對其封裝,從而使得切換任務(wù)變的更加簡單
gevent
greenlet已經(jīng)實現(xiàn)了協(xié)程,但是這個還的人工切換,是不是覺得太麻煩了,不要捉急,python還有一個比greenlet更強大的并且能夠自動切換任務(wù)的模塊gevent
其原理是當(dāng)一個greenlet遇到IO(指的是input output 輸入輸出,比如網(wǎng)絡(luò)、文件操作等)操作時,比如訪問網(wǎng)絡(luò),就自動切換到其他的greenlet,等到IO操作完成,再在適當(dāng)?shù)臅r候切換回來繼續(xù)執(zhí)行。
由于IO操作非常耗時,經(jīng)常使程序處于等待狀態(tài),有了gevent為我們自動切換協(xié)程,就保證總有g(shù)reenlet在運行,而不是等待IO
進(jìn)程是資源分配的單位
線程是操作系統(tǒng)調(diào)度的單位
進(jìn)程切換需要的資源很最大,效率很低
線程切換需要的資源一般,效率一般(當(dāng)然了在不考慮GIL的情況下)
協(xié)程切換任務(wù)資源很小,效率高
多進(jìn)程、多線程根據(jù)cpu核數(shù)不一樣可能是并行的,但是協(xié)程是在一個線程中所以是并發(fā)的
JavaScript
JavaScript 是網(wǎng)絡(luò)上最常用也是支持者最多的客戶端腳本語言。它可以收集 用戶的跟蹤數(shù)據(jù),不需要重載頁面直接提交表單,在頁面嵌入多媒體文件,甚至運行網(wǎng)頁游戲。
我們可以在網(wǎng)頁源代碼的<scripy>標(biāo)簽里看到,比如:
<script type="text/javascript" src="https://statics.huxiu.com/w/mini/static_2015/js/sea.js?v=201601150944"></script>
jQuery
jQuery 是一個十分常見的庫,70% 最流行的網(wǎng)站(約 200 萬)和約 30% 的其他網(wǎng)站(約 2 億)都在使用。一個網(wǎng)站使用 jQuery 的特征,就是源代碼里包含了 jQuery 入口,比如:
src="https://statics.huxiu.com/w/mini/static_2015/js/jquery-1.11.1.min.js?v=201512181512"></script>
Ajax
我們與網(wǎng)站服務(wù)器通信的唯一方式,就是發(fā)出 HTTP 請求獲取新頁面。如果提交表單之后,或從服務(wù)器獲取信息之后,網(wǎng)站的頁面不需要重新刷新,那么你訪問的網(wǎng)站就在用Ajax 技術(shù)。
Ajax 其實并不是一門語言,而是用來完成網(wǎng)絡(luò)任務(wù)(可以認(rèn)為 它與網(wǎng)絡(luò)數(shù)據(jù)采集差不多)的一系列技術(shù)。Ajax 全稱是 Asynchronous JavaScript and XML(異步 JavaScript 和 XML),網(wǎng)站不需要使用單獨的頁面請求就可以和網(wǎng)絡(luò)服務(wù)器進(jìn)行交互 (收發(fā)信息)。
DHTML
Ajax 一樣,動態(tài) HTML(Dynamic HTML, DHTML)也是一系列用于解決網(wǎng)絡(luò)問題的 技術(shù)集合。DHTML 是用客戶端語言改變頁面的 HTML 元素(HTML、CSS,或者二者皆 被改變)。比如頁面上的按鈕只有當(dāng)用戶移動鼠標(biāo)之后才出現(xiàn),背景色可能每次點擊都會改變,或者用一個 Ajax 請求觸發(fā)頁面加載一段新內(nèi)容,網(wǎng)頁是否屬于DHTML,關(guān)鍵要看有沒有用 JavaScript 控制 HTML 和 CSS 元素。
Selenium
Selenium是一個Web的自動化測試工具,最初是為網(wǎng)站自動化測試而開發(fā)的,類型像我們玩游戲用的按鍵精靈,可以按指定的命令自動操作,不同是Selenium 可以直接運行在瀏覽器上,它支持所有主流的瀏覽器(包括PhantomJS這些無界面的瀏覽器)。 Selenium 可以根據(jù)我們的指令,讓瀏覽器自動加載頁面,獲取需要的數(shù)據(jù),甚至頁面截屏,或者判斷網(wǎng)站上某些動作是否發(fā)生。
Selenium 自己不帶瀏覽器,不支持瀏覽器的功能,它需要與第三方瀏覽器結(jié)合在一起才能使用。但是我們有時候需要讓它內(nèi)嵌在代碼中運行,所以我們可以用一個叫 PhantomJS 的工具代替真實的瀏覽器。
Selenium 庫里有個叫 WebDriver 的 API。WebDriver 有點兒像可以加載網(wǎng)站的瀏覽器,但是它也可以像 BeautifulSoup 或者其他 Selector 對象一樣用來查找頁面元素,與頁面上的元素進(jìn)行交互 (發(fā)送文本、點擊等),以及執(zhí)行其他動作來運行網(wǎng)絡(luò)爬蟲。
注意: 我們使用的有界面瀏覽器,它雖然方便我們觀察,但是在實際運用中是非常消耗性能的 我們也可以使用Chrome的無界面瀏覽器,除了沒有瀏覽器界面以外,其它的相關(guān)操作都與有界面瀏覽器相同
- 獲取id標(biāo)簽值
element = driver.find_element_by_id("passwd-id")
- 獲取name標(biāo)簽值
element = driver.find_element_by_name("user-name")
- 獲取標(biāo)簽名值
element = driver.find_elements_by_tag_name("input")
- 也可以通過XPath來匹配
element = driver.find_element_by_xpath("http://input[@id='passwd-id']")
Cookies
獲取頁面每個Cookies值,用法如下:
cookies = driver.get_cookies()
for cookie in cookies:
print("%s -> %s" % (cookie['name'], cookie['value']))
cookie_dict = {i['name']:i['value'] for i in cookies}
print(cookie_dict)
添加cookies
driver.add_cookie(cookie_dict)
刪除Cookies,用法如下
- 刪除一個特定的cookie
driver.delete_cookie("CookieName") - 刪除所有cookie
driver.delete_all_cookies()
設(shè)置無頭瀏覽器
opt = webdriver.ChromeOptions()
opt.set_headless()
設(shè)置代理
opt = webdriver.ChromeOptions()
opt.add_argument("--proxy-server=http://118.20.16.82:9999")
數(shù)據(jù)庫基本命令
- 查看當(dāng)前數(shù)據(jù)庫名稱:db
- 列出所有在物理上存在的數(shù)據(jù)庫:show dbs
- 切換數(shù)據(jù)庫 如果數(shù)據(jù)庫不存在,則指向數(shù)據(jù)庫,但不創(chuàng)建,直到插入數(shù)據(jù)或創(chuàng)建集合時數(shù)據(jù)庫才被創(chuàng)建:use 數(shù)據(jù)庫名稱
- 查看當(dāng)前數(shù)據(jù)庫信息:db.stats()
- 數(shù)據(jù)庫刪除:刪除當(dāng)前指向的數(shù)據(jù)庫,如果數(shù)據(jù)庫不存在,則什么也不做:db.dropDatabase()
創(chuàng)建集合
:db.createCollection(name, options)
- 不限制集合大小:db.createCollection("stu")
查看當(dāng)前數(shù)據(jù)庫所有集合
- show collections:當(dāng)前數(shù)據(jù)庫的集合數(shù)
刪除集合
- db.集合名稱.drop() 如果成功刪除選定集合,則 drop() 方法返回 true,否則返回 false
數(shù)據(jù)的增、刪、改、基本查詢
插入文檔
語法
db.集合名稱.insert(document)
插入文檔時,如果不指定_id參數(shù),MongoDB會為文檔分配一個唯一的ObjectId
db.stu.insert({name:'張三',gender:1})
多條插入
db.stu.insert([{name:'小明',gender:1},{name:'小紅',gender:0}])
查詢?nèi)课臋n
語法: db.集合名稱.find()
db.stu.find()
更新文檔
全文檔更新
db.stu.update({name:'xxxxx'},{name:'張xxx'})
指定屬性更新,通過操作符$set
db.stu.insert({name:'李四',gender:1}) db.stu.update({name:'李四'},{$set:{name:'李四'}})
save() 方法
save() 方法通過傳入的文檔來替換已有文檔,如果文檔的_id已經(jīng)存在則修改,如果文檔的_id不存在則添加 db.集合名稱.save(document)
db.stu.save({_id:'20180820101010','name':'保存',gender:1})
刪除文檔
- db.集合名稱.remove(document)
- 只刪除1條,1表示是否只刪除一條為true的意思 db.集合名稱.remove(document,1) db.集合名稱.remove(document,{justOne:true})
- 表示刪除全部 db.集合名稱.remove({})
查詢:
db.集合名稱.find({條件文檔})
方法findOne():查詢,只返回第一個
db.集合名稱.findOne({條件文檔})
方法pretty():將結(jié)果格式化
db.集合名稱.find({條件文檔}).pretty()
查詢出姓名等于李某某的學(xué)生
db.stu.find({name:'李某某'})
Limit與Skip方法
limit() 方法 讀取指定數(shù)量的數(shù)據(jù)記錄
基本語法如下所示:
db.COLLECTION_NAME.find().limit(num)
Skip() 方法 使用skip()方法來跳過指定數(shù)量的數(shù)據(jù),skip方法同樣接受一個數(shù)字參數(shù)作為跳過的記錄條數(shù)。
基本語法格式如下:
db.集合名稱.find().skip(num)
limit() 方法、Skip() 方法 同時使用,不分先后順序 表示跳過多少條,返回多少條
查詢第5至8條數(shù)據(jù)
db.stu.find().limit(4).skip(5)
sort() 方法排序
sort() 方法對數(shù)據(jù)進(jìn)行排序,sort() 方法可以通過參數(shù)指定排序的字段,并使用 1 和 -1 來指定排序的方式,其中 1 為升序排列,而 -1 是用于降序排列。
基本語法如下所示:
升序
db.集合名稱.find().sort({要排序的字段:1})
降序
db.集合名稱.find().sort({要排序的字段:-1})
根據(jù)多個字段排序: 例:先根據(jù)年齡做降序,再根據(jù)性別做升序
db.集合名稱.find().sort({age:-1,gender:1})
Scrapy 框架
- Scrapy是用純Python實現(xiàn)一個為了爬取網(wǎng)站數(shù)據(jù)、提取結(jié)構(gòu)性數(shù)據(jù)而編寫的應(yīng)用框架,用途非常廣泛。
- 框架的力量,用戶只需要定制開發(fā)幾個模塊就可以輕松的實現(xiàn)一個爬蟲,用來抓取網(wǎng)頁內(nèi)容以及各種圖片,非常之方便。
- Scrapy 使用了 Twisted['tw?st?d] 異步網(wǎng)絡(luò)框架來處理網(wǎng)絡(luò)通訊,可以加快我們的下載速度,不用自己去實現(xiàn)異步框架,并且包含了各種中間件接口,可以靈活的完成各種需求。
異步:調(diào)用在發(fā)出之后,這個調(diào)用就直接返回,不管有無結(jié)果 非阻塞:關(guān)注的是程序在等待調(diào)用結(jié)果(消息,返回值)時的狀態(tài),指在不能立刻得到結(jié)果之前,該調(diào)用不會阻塞當(dāng)前線程
- Scrapy Engine(引擎): 負(fù)責(zé)Spider、ItemPipeline、Downloader、Scheduler中間的通訊,信號、數(shù)據(jù)傳遞等。
- Scheduler(調(diào)度器): 它負(fù)責(zé)接受引擎發(fā)送過來的Request請求,并按照一定的方式進(jìn)行整理排列,入隊,當(dāng)引擎需要時,交還給引擎。
- Downloader(下載器):負(fù)責(zé)下載Scrapy Engine(引擎)發(fā)送的所有Requests請求,并將其獲取到的Responses交還給Scrapy Engine(引擎),由引擎交給Spider來處理,
- Spider(爬蟲):它負(fù)責(zé)處理所有Responses,從中分析提取數(shù)據(jù),獲取Item字段需要的數(shù)據(jù),并將需要跟進(jìn)的URL提交給引擎,再次進(jìn)入Scheduler(調(diào)度器),
- Item Pipeline(管道):它負(fù)責(zé)處理Spider中獲取到的Item,并進(jìn)行進(jìn)行后期處理(詳細(xì)分析、過濾、存儲等)的地方.
- Downloader Middlewares(下載中間件):你可以當(dāng)作是一個可以自定義擴展下載功能的組件。
- Spider Middlewares(Spider中間件):你可以理解為是一個可以自定擴展和操作引擎和Spider中間通信的功能組件(比如進(jìn)入Spider的Responses;和從Spider出去的Requests)
制作 Scrapy 爬蟲 一共需要4步:
- 新建項目
scrapy startproject 爬蟲項目名稱
新建一個新的爬蟲
明確目標(biāo)
(編寫items.py):明確你想要抓取的目標(biāo)
- 制作爬蟲
scrapy genspider 爬蟲文件名稱 域名:制作爬蟲開始爬取網(wǎng)頁
- 存儲內(nèi)容
(pipelines.py):設(shè)計管道存儲爬取內(nèi)容
關(guān)于爬蟲部分一些建議:
- 盡量減少請求次數(shù),能抓列表頁就不抓詳情頁,減輕服務(wù)器壓力,程序員都是混口飯吃不容易。
- 當(dāng)web網(wǎng)站反爬蟲手段比較嚴(yán)格時,不要只看 Web 網(wǎng)站,還有手機 App 和 H5,這樣的反爬蟲措施一般比較少。
- 實際應(yīng)用時候,之前一般防守方做到根據(jù) IP 限制頻次就結(jié)束了,除非很核心的數(shù)據(jù),不會再進(jìn)行更多的驗證,畢竟成本的問題會考慮到還要考慮到用戶體驗。
- 如果真的對性能要求很高,可以考慮多線程、多進(jìn)程(一些成熟的框架如 Scrapy都已支持),甚至分布式...