Python常用的爬蟲框架、解析方式、存儲方式對比

00 概況


以安居客杭州二手房信息為爬蟲需求,分別對比實驗了三種爬蟲框架、三種字段解析方式和三種數據存儲方式,旨在全方面對比各種爬蟲方式的效率高低。


安居客平臺沒有太強的反爬措施,只要添加headers模擬頭即可完美爬取,而且不用考慮爬蟲過快的問題。選中杭州二手房之後,很容易發現url的變化規律。值得說明的是平臺最大開放50頁房源信息,每頁60條。為使爬蟲簡單便於對比,我們只爬取房源列表頁的概要信息,而不再進入房源詳情頁進行具體信息的爬取,共3000條記錄,每條記錄包括10個字段:標題,戶型,面積,樓層,建築年份,小區/地址,售賣標籤,中介,單價,總價。



01 3種爬蟲框架


1. 常規爬蟲

實現3個函數,分別用於解析網頁、存儲信息,以及二者的聯合調用。在主程序中,用一個常規的循環語句逐頁解析。


<code>import requests
from lxml import etree
import pymysql
import time

def get_info(url):
    pass
    return infos

def save_info(infos):
    passdef getANDsave(url):    passif __name__ == '__main__':    urls = [f'https://hangzhou.anjuke.com/sale/p{page}/' for page in range(1,51)]    start = time.time()    #常規單線程爬取    for url in urls:        getANDsave(url)    tt = time.time()-start    print("共用時:",tt, "秒。")/<code>

耗時64.9秒。


2. Scrapy框架

Scrapy框架是一個常用的爬蟲框架,非常好用,只需要簡單實現核心抓取和存儲功能即可,而無需關注內部信息流轉,而且框架自帶多線程和異常處理能力。

<code>class anjukeSpider(scrapy.Spider):
    name = 'anjuke'
    allowed_domains = ['anjuke.com']
    start_urls = [f'https://hangzhou.anjuke.com/sale/p{page}/' for page in range(1, 51)]


    def parse(self, response):
        pass
        yield item/<code>

耗時14.4秒。


3. 多線程爬蟲

對於爬蟲這種IO密集型任務來說,多線程可明顯提升效率。實現多線程python的方式有多種,這裡我們應用concurrent的futures模塊,並設置最大線程數為8。


<code>from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED

def get_info(url):
    pass
    return infos

def save_info(infos):
    pass

def getANDsave(url):
    pass

if __name__ == '__main__':
    urls = [f'https://hangzhou.anjuke.com/sale/p{page}/' for page in range(1,51)]
    start = time.time()
    executor = ThreadPoolExecutor(max_workers=8)
    future_tasks = [executor.submit(getANDsave, url) for url in urls]
    wait(future_tasks, return_when = ALL_COMPLETED)
    tt = time.time()-start
    print("共用時:",tt, "秒。")/<code>

耗時8.1秒。


對比來看,多線程爬蟲方案耗時最短,相比常規爬蟲而言能帶來數倍的效率提升,Scrapy爬蟲也取得了不俗的表現。需要指出的是,這裡3種框架都採用了Xpath解析和MySQL存儲。




02 3種解析方式


在明確爬蟲框架的基礎上,如何對字段進行解析提取就是第二個需要考慮的問題,常用的解析方式有3種,一般而言,論解析效率Re>=Xpath>Bs4;論難易程度,Bs4則最為簡單易懂。


因為前面已經對比得知,多線程爬蟲有著最好的執行效率,我們以此為基礎,對比3種不同解析方式,解析函數分別為:


1. Xpath


<code>from lxml import etreedef get_info(url):
    response = requests.get(url, headers = headers)
    html = response.text
    html = etree.HTML(html)
    items = html.xpath("//li[@class = 'list-item']")
    infos = []
    for item in items:
        try:
            title = item.xpath(".//div[@class='house-title']/a/text()")[0].strip()
            houseType = item.xpath(".//div[@class='house-details']/div[2]/span[1]/text()")[0]
            area = item.xpath(".//div[@class='house-details']/div[2]/span[2]/text()")[0]
            floor = item.xpath(".//div[@class='house-details']/div[2]/span[3]/text()")[0]
            buildYear = item.xpath(".//div[@class='house-details']/div[2]/span[4]/text()")[0]
            adrres = item.xpath(".//div[@class='house-details']/div[3]/span[1]/text()")[0]
            adrres = "|".join(adrres.split())
            tags = item.xpath(".//div[@class='tags-bottom']//text()")
            tags = '|'.join(tags).strip()
            broker = item.xpath(".//div[@class='broker-item']/span[2]/text()")[0]
            totalPrice = item.xpath(".//div[@class='pro-price']/span[1]//text()")
            totalPrice = "".join(totalPrice).strip()
            price = item.xpath(".//div[@class='pro-price']/span[2]/text()")[0]
            values = (title, houseType, area, floor, buildYear, adrres, tags, broker, totalPrice, price)
            infos.append(values)
        except:
            print('1條信息解析失敗')
    return infos/<code>

耗時8.1秒。


2. Re


<code>import redef get_info(url):
    response = requests.get(url, headers = headers)
    html = response.text
    html = html.replace('\\n','')
    pattern = r'
  • .*?
  • '
        results = re.compile(pattern).findall(html)##先編譯,再正則匹配
        infos = []

        for result in results: 
            values = ['']*10
            titles = re.compile('title="(.*?)"').findall(result)
            values[0] = titles[0]
            values[5] = titles[1].replace(' ','')
            spans = re.compile('(.*?)|(.*?)|(.*?)|(.*?)').findall(result)
            values[1] =''.join(spans[0])
            values[2] = ''.join(spans[1])
            values[3] = ''.join(spans[2])
            values[4] = ''.join(spans[3])
            values[7] = re.compile('(.*?)').findall(result)[0]
            tagRE = re.compile('(.*?)').findall(result)
            if tagRE:
                values[6] = '|'.join(tagRE)
            values[8] = re.compile('(.*?)萬').findall(result)[0]+'萬'
            values[9] = re.compile('(.*?)').findall(result)[0]
            infos.append(tuple(values))

        return infos/<code>

    耗時8.6秒。


    3. Bs4


    <code>from bs4 import BeautifulSoupdef get_info(url):
        response = requests.get(url, headers = headers)
        html = response.text
        soup = BeautifulSoup(html, "html.parser")
        items = soup.find_all('li', attrs={'class': "list-item"})
        infos = []
        for item in items:
            try:
                title = item.find('a', attrs={'class': "houseListTitle"}).get_text().strip()
                details = item.find_all('div',attrs={'class': "details-item"})[0]
                houseType = details.find_all('span')[0].get_text().strip()
                area = details.find_all('span')[1].get_text().strip()

                floor = details.find_all('span')[2].get_text().strip()
                buildYear = details.find_all('span')[3].get_text().strip()
                addres = item.find_all('div',attrs={'class': "details-item"})[1].get_text().replace(' ','').replace('\\n','')
                tag_spans = item.find('div', attrs={'class':'tags-bottom'}).find_all('span')
                tags = [span.get_text() for span in tag_spans]
                tags = '|'.join(tags)
                broker = item.find('span',attrs={'class':'broker-name broker-text'}).get_text().strip()
                totalPrice = item.find('span',attrs={'class':'price-det'}).get_text()
                price = item.find('span',attrs={'class':'unit-price'}).get_text()
                values = (title, houseType, area, floor, buildYear, addres, tags, broker, totalPrice, price)
                infos.append(values)
            except:
                print('1條信息解析失敗')
        return infos/<code>

    耗時23.2秒。


    Xpath和Re執行效率相當,Xpath甚至要略勝一籌,Bs4效率要明顯低於前兩者(此案例中,相當遠前兩者效率的1/3),但寫起來則最為容易。



    03 存儲方式


    在完成爬蟲數據解析後,一般都要將數據進行本地存儲,方便後續使用。小型數據量時可以選用本地文件存儲,例如CSV、txt或者json文件;當數據量較大時,則一般需採用數據庫存儲,這裡,我們分別選用關係型數據庫的代表MySQL和文本型數據庫的代表MongoDB加入對比。


    1. MySQL


    <code>import pymysql

    def save_info(infos):
        #####infos為列表形式,其中列表中每個元素為一個元組,包含10個字段
        db= pymysql.connect(host="localhost",user="root",password="123456",db="ajkhzesf")
        sql_insert = 'insert into hzesfmulti8(title, houseType, area, floor, buildYear, adrres, tags, broker, totalPrice, price) values(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)'
        cursor = db.cursor()
        cursor.executemany(sql_insert, infos)
        db.commit()/<code>

    耗時8.1秒。


    2. MongoDB


    <code>import pymongo

    def save_info(infos):
        # infos為列表形式,其中列表中的每個元素為一個字典,包括10個字段
        client = pymongo.MongoClient()
        collection = client.anjuke.hzesfmulti
        collection.insert_many(infos)
        client.close()/<code>

    耗時8.4秒。


    3. CSV文件


    <code>import csv

    def save_info(infos):
        # infos為列表形式,其中列表中的每個元素為一個列表,包括10個字段
        with open(r"D:\\PyFile\\HZhouse\\anjuke.csv", 'a', encoding='gb18030', newline="") as f:
            writer = csv.writer(f)
            writer.writerows(infos)/<code>

    耗時8.8秒。


    可見,在爬蟲框架和解析方式一致的前提下,不同存儲方式間並不會帶來太大效率上的差異。




    04 結論


    Python常用的爬蟲框架、解析方式、存儲方式對比

    不同爬蟲執行效率對比


    易見,爬蟲框架對耗時影響最大,甚至可帶來數倍的效率提升;解析數據方式也會帶來較大影響,而數據存儲方式則不存在太大差異。

    需要免費資料的可以私信小編關鍵字“學習”就可以領取了!


    分享到:


    相關文章: