公司里是怎麼做數據抓取的? --- 搜狗詞庫抓取&解析
不知不覺已經上了三周的班了,也寫了幾個小項目啦, 這裡和大家分享一個比較有意思的抓取項目:搜狗輸入法詞庫爬取,和 大家說說在公司里都是怎麼寫項目的
導讀
本次抓取的內容是:搜狗的全部詞庫
地址:https://pinyin.sogou.com/dict/cate/index/167
這次有點複雜需要一些前置的知識:
- 資料庫表設計
- 資料庫的讀取
- 日誌模塊運用
- 多線程的運用
- 隊列的運用
- 字元串編碼&解碼
- ...
項目文件梳理:
PS:這裡的代碼我是自己重構過,
公司里的代碼要用內部框架來抓取。所以不能分享出來啦吐個槽: 我司還在用Python2.7,編碼的問題要把我搞死了!!
我猜是因為2.7的print 可以少打一對括弧才一直不升級 逃~來看一下項目結構:
├── configs.py # 資料庫的配置文件n├── jiebao.py # 搜狗的詞庫是加密的,用來解密詞庫用的n├── spidern│ ├── log_SougouDownloader.log.20171118 # 詞庫下載日誌n│ ├── log_SougouSpider.log.20171118 # 抓取日誌n│ └── spider.py # 爬蟲文件n├── store_newn│ ├── __init__.pyn│ └── stroe.py # 資料庫操作的封裝n└── utilsn └── tools.py # 日誌模塊n
整體的還是比較清晰的
源文件我放在GitHub里:https://github.com/Ehco1996/Python-crawler/tree/master/sougou
爬蟲邏輯&資料庫表設計
來看一下入口的網頁結構:
我們需要解析 :
- 一級分類
- 二級分類
- 文件名
- 真實下載地址
由於數據量較大
有約1萬個詞庫文件需要下載
有約 1億 個關鍵詞需要解析不能將所有邏輯耦合在一起這裡我建立三張資料庫表:
- 存儲詞庫二級分類入口的 cate 表:
- 存儲所有下載地址的 detail 表
- 存儲所有解析後關鍵詞的keyword表
所以整體的邏輯是這樣的:
- 抓取二級分類的入口地址 寫入cate 表
- 從cate 表 讀取地址發送請求後,解析下載地址存入detail表
- 從detail 表 讀取詞庫的下載地址下載詞庫到文件
- 從本地讀文件解析成關鍵詞記錄存入keyword 表
代碼部分
首先是二級cate頁的解析
def cate_ext(self, html, type1):n n 解析列表頁的所有分類名n Args:n html 文本n type1 一級目錄名 n n res = []n soup = BeautifulSoup(html, lxml)n cate_list = soup.find(div, {id: dict_cate_show})n lis = cate_list.find_all(a)n for li in lis:n type2 = li.text.replace(", )n url = http://pinyin.sogou.com + li[href] + /default/{}n res.append({n url: url,n type1: type1,n type2: type2,n })n return resn
這裡我通過解析一級分類的入口頁來獲取所有二級分類的地址
詞庫文件下載地址的解析:
def list_ext(self, html, type1, type2):n n 解析搜狗詞庫的列表頁面n args:n html: 文本n type1 一級目錄名 n type2 二級目錄名 n retrun listn 每一條數據都為字典類型n n res = []n try:n soup = BeautifulSoup(html, lxml)n # 偶數部分n divs = soup.find_all("div", class_=dict_detail_block)n for data in divs:n name = data.find(div, class_=detail_title).a.textn url = data.find(div, class_=dict_dl_btn).a[href]n res.append({filename: type1 + _ + type2 + _ + name,n type1: type1,n type2: type2,n url: url,n })n # 奇數部分n divs_odd = soup.find_all("div", class_=dict_detail_block odd)n for data in divs_odd:n name = data.find(div, class_=detail_title).a.textn url = data.find(div, class_=dict_dl_btn).a[href]n res.append({filename: type1 + _ + type2 + _ + name,n type1: type1,n type2: type2,n url: url,n })n except:n print(解析失敗)n return - 1n return resn
爬蟲的入口:
def start(self):n n 解析搜狗詞庫的下載地址和分類名稱n n # 從資料庫讀取二級分類的入口地址n cate_list = self.store.find_all(sougou_cate)n for cate in cate_list:n type1 = cate[type1]n type2 = cate[type2]n for i in range(1, int(cate[page]) + 1):n print(正在解析{}的第{}頁.format(type1 + type2, i))n url = cate[url].format(i)n html = get_html_text(url)n if html != -1:n res = self.list_ext(html, type1, type2)n self.log.info(正在解析頁面 {}.format(url))n for data in res:n self.store.save_one_data(sougou_detail, data)n self.log.info(正在存儲數據{}.format(data[filename]))n time.sleep(3)n
我這裡通過從cate表裡讀取記錄,來發請求並解析出下載地址
對於資料庫操作的封裝,可以直接閱讀我 store_new/store.py
這個文件
詞庫文件下載邏輯:
def start(self):n # 從資料庫檢索記錄n res = self.store.find_all(sougou_detail)n self.log.warn(一共有{}條詞庫等待下載.format(len(res)))n for data in res:n content = self.get_html_content(data[url])n filename = self.strip_wd(data[filename])n # 如果下載失敗,我們等三秒再重試n if content == -1:n time.sleep(3)n self.log.info({}下載失敗 正在重試.format(filename))n content = self.get_html_content(data[1])n self.download_file(content, filename)n self.log.info(正在下載文件{}.format(filename))n time.sleep(1)n
這個部分沒有什麼難的,主要涉及到文件的讀寫操作
解析詞庫文件邏輯:
def start():n # 使用多線程解析n threads = list()n # 讀文件存入queue的線程n threads.append(n Thread(target=ext_to_queue))nn # 存資料庫的線程n for i in range(10):n threads.append(n Thread(target=save_to_db))n for thread in threads:n thread.start()n
好了,所有的步驟都已經過了一遍
下面我們來說一些裡面的「坑」Logging日誌模塊的運用
由於需要爬取和下載的數量比較大,
沒有log是萬萬不行的,畢竟我們不能時刻定在電腦面前看程序輸出吧!實際上,我們寫的程序基本是跑在伺服器上的。我都是通過看程序的log文件來判斷運行的狀態。在之前的代碼里,可以看到我在很多地方都打了log來看看日誌文件都長啥樣:spider日誌
downloader日誌
運行狀態是不是一目了然了呢?
想知道怎麼實現的可以看utils/tools.py
這個文件其實就是用了Python自帶的logging模塊
多線程的使用
詞庫文件下載下來一共9000+個
平均一個詞庫有2w條關鍵詞那麼總共就是1.8億條數據需要存儲假設存儲一秒鐘可以存儲10條數據
那麼這麼多數據需要存:5000+小時想想都可怕,如果真的要這麼久,那黃花菜都涼了。這裡就需要我們用多線程來進行操作了,
由於涉及到文件的讀寫我們得利用隊列(queue)來幫助我們完成需求邏輯是這樣的:
首先開一條主線程對本地的詞庫文件進行讀取/解析:
- 從文件夾讀取詞庫文件到內存
- 解析詞庫文件,並將每個關鍵詞存入隊列之中
- 周而復始~
其次開10~50條線程(取決於資料庫的最大連接數)對隊列進行操作:
- 不停的從隊列中取出一條記錄,並存入資料庫
- 如果隊列是空的,就sleep一會,等待主線程解析
- 周而復始~
由於篇幅的長度,我只將從隊列取數據的代碼片段放出來
想要整個邏輯的可以去閱讀jiebao.py
def save_to_db():n n 從數據隊列里拿一條數據n 並存入資料庫n n store = DbToMysql(configs.TEST_DB)n while True:n try:n st = time.time()n data = res_queue.get_nowait()n t = int(time.time() - st)n if t > 5:n print("res_queue", t)n save_data(data, store)n except:n print("queue is empty wait for a while")n time.sleep(2)n
利用多線程之後,速度一下快了幾十倍
經過測試:1秒能存500條左右最後來看一下資料庫里解析的關鍵詞:
跑了一段時間之後,已經有4千萬的數據啦
好了,這個項目就分享到這裡,
還有很多細節部分得自己去看代碼啦每天的學習記錄都會 同步更新到:
微信公眾號: findyourownway 知乎專欄:https://zhuanlan.zhihu.com/Ehco-python Blog : www.ehcoblog.ml GitHub: https://github.com/Ehco1996/Python-crawler推薦閱讀:
※Python系列之——利用Python實現微博監控
※Python入門進階推薦書單
※切爾西隊史上第一次連續三場0比0,重點是...
※特朗普退出《巴黎協定》:python詞雲圖輿情分析
※winpython, anaconda 哪個更好?