公司里是怎麼做數據抓取的? --- 搜狗詞庫抓取&解析

不知不覺已經上了三周的班了,也寫了幾個小項目啦, 這裡和大家分享一個比較有意思的抓取項目:搜狗輸入法詞庫爬取,和 大家說說在公司里都是怎麼寫項目的

導讀

本次抓取的內容是:搜狗的全部詞庫

地址:pinyin.sogou.com/dict/c

這次有點複雜需要一些前置的知識:

  • 資料庫表設計
  • 資料庫的讀取
  • 日誌模塊運用
  • 多線程的運用
  • 隊列的運用
  • 字元串編碼&解碼
  • ...

項目文件梳理:

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里:

github.com/Ehco1996/Pyt

爬蟲邏輯&資料庫表設計

來看一下入口的網頁結構:

我們需要解析 :

  • 一級分類
  • 二級分類
  • 文件名
  • 真實下載地址

由於數據量較大

有約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這個文件

也可以看我上周寫的文章:zhuanlan.zhihu.com/p/30

詞庫文件下載邏輯:

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

知乎專欄:zhuanlan.zhihu.com/Ehco

Blog : www.ehcoblog.ml

GitHub: github.com/Ehco1996/Pyt

推薦閱讀:

Python系列之——利用Python實現微博監控
Python入門進階推薦書單
切爾西隊史上第一次連續三場0比0,重點是...
特朗普退出《巴黎協定》:python詞雲圖輿情分析
winpython, anaconda 哪個更好?

TAG:爬虫 | Python | 搜狗 |