Python爬蟲-速度(1)
Python爬蟲-速度(1)
文章目錄
018.9.16
前言
其實爬蟲的整個基本流程已經講完了。無論是如何發起請求,還是解析檔案,再到儲存,以及處理需要js渲染的網頁。入門需要掌握的,也不過這些而已。只是可能還不夠,比方說速度。在我們不想用框架,如scrapy,但仍想為程式提速的時候,應該怎樣解決呢?
我認為大概可以從多程序,多執行緒,協程,以及非同步上進行考慮。但我不建議把速度提升到極致,這裡有兩個理由:第一,同一IP短時間內請求頻率過高,會帶來封IP的反扒策略,當然,你可以使用海量代理IP;第二,也是我認為更重要的一點,請求頻率過高可能會造成網站伺服器宕機,對方運維人員日子會不好過的,大家都是混口飯吃,所以何必。
【速度篇】預計分三部分來寫吧,第一部分主要寫一個普通的爬蟲程式,第二部分會是關於什麼是多程序執行緒之類的,第三部分則是前面的結合——用第二部分中的內容來改寫第一部分的爬蟲程式。最後通過列印程式執行時間,來簡單驗證速度是否得到提升。
程式是講究實用性的。老實說,我感覺自己在爬蟲裡遇到了瓶頸,不但是技術上的,更是使用上的。可以用個類比來解釋我現在的困境:因為覺得無處使力,而不知自己力的天花板,所以也無法通過修煉來提升自己的力氣。我確實沒什麼要爬取的資料了,這是早晚的事,畢竟我是出於興趣而非任務,爬完自己的“興趣”,便不知路往何方。
但生活總有給你一個“偶爾”使力的機會,只要你能發現。
最近秋招開始,學校的就業資訊網陸續更新了一些公司的宣講會時間,教室,職位要求等。而每次開啟瀏覽器去閱覽是有點麻煩的事兒,因為學校的每個網站都不以使用者的角度思考。這些不必細說,想你們也是深有體會的。所以今天的爬蟲程式就以此為背景,將所有職位爬取下來儲存本地。為每個公司建立一個資料夾,裡邊包含職位詳情的txt檔案,以及招聘簡章的doc檔案,具體效果如下:
網頁分析
目錄頁(http://www.swpu.edu.cn/zsjy/info/1056/1922.htm)
點選職位對應的招聘連結,即進入詳情頁
詳情頁頁底,會有招聘簡章的連結,一般為docx檔案,少部分會是pdf或者excel,或者沒有。由於這個爬蟲程式主要用來“改寫”,所以按“少數服從多數”策略,都以docx檔案來處理
介面設計
首先我寫了一個函式,用來請求網頁
def get_one_html(url, tries=3):
"""獲取一個網頁"""
try:
response = requests.get(url, headers=HEADERS)
except error.HTTPError:
if tries <= 0:
return None
else:
get_one_html(url, tries-1)
else:
response.encoding = response.apparent_encoding
bag.html = response.text
bag.url = response.url
return bag
該函式返回一個bag包,其實是一種類似tuple的資料結構,建立方式:
bag = namedtuple("html_link", ["html", "url"])
之後解析目錄頁的資料,主要提取公司名,宣講會時間,宣講會地點,詳情頁連結(其實中間兩個並沒有用到)
def extract_html(html):
"""提取網頁內容"""
selector = etree.HTML(html)
roots = selector.xpath('//div[@id="vsb_content"]//tbody/tr')
for root in roots[1:]:
company = root.xpath("./td[1]/text()")
time = root.xpath("./td[2]/text()")
position = root.xpath("./td[3]/text()")
link = root.xpath("./td[4]/a/@href")
yield {
"company": "".join(company),
"time": "".join(time),
"position": "".join(position),
"link": "".join(link),
}
該函式是yield出去一個字典,實際上使用了協程的思想
既然拿到了詳情頁連結,自然是要請求這個連結拿到html的,請求網頁依然可以讓get_one_html()
函式來負責;解析則需要另寫一個函式
def extract_detail_html(bag):
"""提取詳情頁內容"""
doc = pq(bag.html)
data = doc("#vsb_content_100 tr").text()
# 文字資訊
result = map(lambda x: x.replace("\n", ":"), [item for item in data.split(" ") if item])
# 招聘簡章連結
link = doc(".Main tr td span > a").attr("href")
return (result, parse.urljoin(bag.url, link))
這裡解析用的是PyQuery庫,這是因為它的一個text()
方法可以直接拿出某個節點,以及其子節點,孫節點中的文字資訊,很是方便。該函式返回文字,以及拼接過的docx檔案連結
最後是將資料儲存到本地
def save_to_txt(dirname, url, data):
"""將資料儲存到txt檔案中
目錄名,詳情頁url, 詳情頁內容"""
with open("data/{}/詳情.txt".format(dirname), "w", encoding="utf-8") as file:
for item in chain([url], data):
file.write("{0}\n{1}\n".format(item, "-"*50))
def download_doc(dirname, url, referer):
"""下載對應的doc文件
目錄名,doc的url,詳情頁的url"""
headers = {"Referer": referer}
headers.update(HEADERS)
try:
response = requests.get(url, headers=headers)
except error.HTTPError:
pass
else:
# 二進位制檔案需要wb
with open("data/{}/招聘簡章.doc".format(dirname), "wb") as file:
file.write(response.content)
這裡主要需要注意的是【招聘簡章】的連結,如果我們直接用這個連結去訪問,會跳轉到輸入驗證碼的頁面,但如果在詳情頁點選這個連結,就能直接下載。原因是請求頭需要加入{“Referer”: referer},這個referer值就是當前詳情頁的連結
程式完整如下:
執行效果
執行之後效果如下:
耗時約為16.5秒
此次程式碼已上傳GitHub