這是全部的調(diào)試過程,我已經(jīng)整理成為筆記药版,這里分享給大家:
python爬取豆瓣兩千萬圖書簡介信息:(一)目標(biāo)API分析
python爬取豆瓣兩千萬圖書簡介信息:(二)簡單python請求urllib2
python爬取豆瓣兩千萬圖書簡介信息:(三)異常處理
python爬取豆瓣兩千萬圖書簡介信息:(四)多進(jìn)程并發(fā)
python爬取豆瓣兩千萬圖書簡介信息:(五)數(shù)據(jù)庫設(shè)計(jì)
python爬取豆瓣兩千萬圖書簡介信息:(六)數(shù)據(jù)庫操作類
python爬取豆瓣兩千萬圖書簡介信息:(七)代理IP
python爬取豆瓣兩千萬圖書簡介信息:(八)總結(jié)
多進(jìn)程并發(fā)
我寫的python爬取數(shù)據(jù)程序,爬取的目標(biāo)很明確毁涉,就是爬取豆瓣API的所能提供的 兩千萬圖書簡介信息她混。
計(jì)劃是用python發(fā)起網(wǎng)絡(luò)請求拳话,然后解析數(shù)據(jù)歹茶,并將數(shù)據(jù)放到mysql數(shù)據(jù)庫中夕玩。
如果是簡單的弄個(gè)2kw的for循環(huán)等著依次執(zhí)行你弦,那平均2s一次的請求,會(huì)將時(shí)間拉長到你懷疑人生燎孟。
串行執(zhí)行的路走不通禽作,那就必然會(huì)想到并發(fā)執(zhí)行。在別的程序語言中揩页,多線程是一種很好的并發(fā)策略旷偿。然而,Python由于有全鎖局的存在(同一時(shí)間只能有一個(gè)線程執(zhí)行)爆侣,并不能利用多核優(yōu)勢萍程。所以,如果程序的多線程進(jìn)程是CPU密集型的累提,那多線程并不能帶來效率上的提升尘喝,相反還可能會(huì)因?yàn)榫€程的頻繁切換,導(dǎo)致效率下降斋陪;如果是IO密集型,多線程進(jìn)程可以利用IO阻塞等待時(shí)的空閑時(shí)間執(zhí)行其他線程置吓,提升效率无虚。
我想要的是,同一瞬時(shí)時(shí)間內(nèi)衍锚,盡可能的多開網(wǎng)絡(luò)請求友题,這樣就能提高單位時(shí)間內(nèi),從豆瓣接口內(nèi)爬取數(shù)據(jù)的效率戴质。多線程由于要等待網(wǎng)絡(luò)請求返回的時(shí)間度宦,在這里并不適用。所以我這里采用的是多進(jìn)程的思路告匠。
其實(shí)在python網(wǎng)絡(luò)并發(fā)過程中戈抄,有多協(xié)程的方法來提示效率。但協(xié)程是一種用戶態(tài)的輕量級線程后专。它無法利用多核資源:協(xié)程的本質(zhì)是個(gè)單線程,它不能同時(shí)將 單個(gè)CPU 的多個(gè)核用上,協(xié)程需要和進(jìn)程配合才能運(yùn)行在多CPU上.其效率相對來講划鸽,還是低于多進(jìn)程的方式。
我的思路是戚哎,同時(shí)開200個(gè)到400個(gè)進(jìn)程裸诽,將2kw圖書分配給這幾百個(gè)進(jìn)程。幾百個(gè)進(jìn)程同時(shí)執(zhí)行型凳,自然效率上會(huì)高很多丈冬。當(dāng)然,我自己的mac的CPU也就8核心的配置甘畅。多進(jìn)程也就是能把這8個(gè)核心的利用率提高一點(diǎn)點(diǎn)而已埂蕊。但是实夹,由于我的每一次請求數(shù)據(jù),大多耗時(shí)在網(wǎng)絡(luò)請求中粒梦,所以亮航,這樣使用多進(jìn)程,反而能在某種意義上匀们,提高了相應(yīng)的效率缴淋。
我單次網(wǎng)絡(luò)請求,加上代理ip泄朴,讀取&解析重抖,以及存入數(shù)據(jù)庫,總共耗時(shí)在3s左右祖灰。我開到了200個(gè)進(jìn)程钟沛,總速度大概在5w條/小時(shí)(這里是指有效記錄,會(huì)有一定概率的網(wǎng)絡(luò)請求異常以及空id的數(shù)據(jù)局扶,這部分大概是有效數(shù)據(jù)的三分之一恨统,總的并發(fā)數(shù)據(jù)量應(yīng)該在6.6w條/小時(shí))。大約每秒13條(事先沒有統(tǒng)計(jì)每秒發(fā)出的請求次數(shù)三妈,事實(shí)上我也沒有地方放此數(shù)據(jù))畜埋。
而于之前相比,我開20個(gè)進(jìn)程畴蒲,平均一小時(shí)7k條有效記錄悠鞍,(大概是每秒1.9條)已經(jīng)快上好多好多了。
下面是代碼:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import Crawler #我的單次爬取程序
import multiprocessing
import SqlOperation #我的數(shù)據(jù)庫操作類
import time
#我的每個(gè)進(jìn)程內(nèi)模燥,執(zhí)行id的順序
def worker(num):
thread_index = SqlOperation.get_thread_index_id(num)
#查詢當(dāng)前第 num 個(gè)進(jìn)程已經(jīng)爬取到最大 id
process_index = num*50000+1000000
# print str(process_index) + ':' + str(thread_index)
if process_index < thread_index:
process_index = thread_index
#獲取當(dāng)前第 num 個(gè)進(jìn)程咖祭,應(yīng)該開始爬去數(shù)據(jù)的起始 id
Crawler.start_crawler(process_index, num)
#開始爬取數(shù)據(jù),進(jìn)程為第 num 個(gè)蔫骂,起始id為 process_index
done_id_arr = [1, 2, 3, 4, 6, 7, 8, 9, 10, 12, 13, 14, 15, 17, 18, 19, 20, 23, 25, 26, 27, 28, 30, 32, 34, 36, 38, 39, 40, 43, 44, 52, 64, 70, 74, 84, 86, 87, 98, 102, 116, 119]
#已完成爬取數(shù)據(jù)的進(jìn)程id數(shù)組么翰,從數(shù)據(jù)里查到的,但因?yàn)槊看螁?dòng)程序纠吴,此處只執(zhí)行一次硬鞍,就直接硬編碼,沒有寫自動(dòng)獲取的方法
if __name__ == '__main__':
jobs = []
Crawler.ips = Crawler.get_ip_arr()
#獲取代理ip組
# print Crawler.ips
# Crawler.test_ip(1000007)
for i in range(11, 200):
if i in done_id_arr:
# 如果 第 i 個(gè)進(jìn)程的數(shù)據(jù)已經(jīng)爬完了戴已,即 i 在 done_id_arr中固该,
# 說明此進(jìn)程沒有開的必要了,可節(jié)省相應(yīng)資源
pass
else:
# 單開進(jìn)程糖儡,爬取第 i 個(gè)id組的數(shù)據(jù)
p = multiprocessing.Process(target=worker, args=(i,))
jobs.append(p)
p.start()
執(zhí)行效率前面已經(jīng)說過了伐坏,有效數(shù)據(jù)大概在5w條/小時(shí)。這段程序大概開了四天多握联,最后的數(shù)據(jù)總量是 5645271條有效記錄桦沉。(當(dāng)然數(shù)據(jù)并不是一次就爬成的每瞒,加上之前的調(diào)試異常捕獲,調(diào)試數(shù)據(jù)庫纯露,調(diào)試代理ip剿骨,這些零零碎碎有十幾w的數(shù)據(jù)量,然后程序穩(wěn)定后埠褪,沒有動(dòng)自己跑浓利,連續(xù)不間斷的運(yùn)行時(shí)間大概有三天多)〕伲總的來說贷掖,還是有些成就感的。