1. 程式人生 > >Python爬蟲-速度(1)

Python爬蟲-速度(1)

Python爬蟲-速度(1)

文章目錄


018.9.16

Python爬蟲-速度(2)
Python爬蟲-速度(3)

前言

其實爬蟲的整個基本流程已經講完了。無論是如何發起請求,還是解析檔案,再到儲存,以及處理需要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