• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • python 爬蟲實踐 (爬取鏈家成交房源信息和價格)

    簡單介紹

    pi:
    簡單介紹下,我們需要用到的技術,python 版本是用的pyhon3,系統環境是linux,開發工具是vscode;工具包:request 爬取頁面數據,然后redis 實現數據緩存,lxml 實現頁面數據的分析,提取我們想要的數據,然后多線程和多進程提升爬取速度,最后,通過celery 框架實現分布式爬取,并實際部署下,下面就按這個邏輯順序,進行介紹

    request爬取頁面數據

    開發環境的的安裝這里就不介紹了,大家可以在 去搜索下其他的博客,這類文章很多
    第一步:通過pip 安裝 request: pip install requests;
    我們可以通過help(requests),查看下requests一些基本信息:
    requests一些基本信息
    可以看到這里有兩個方法一個get,一個post,post主要是一些需要提交表單的url,我們這里主要使用get方法,看下實際的代碼吧:

                resp = requests.get(url,headers=header,proxies=proxie,timeout=self.timeOut)
                html = resp.text #text 屬性獲取具體的html文本
                #小于400表示成功了
                if resp.status_code >=400: 
                    html = None
                    #500到600需要重試 400到500是可以直接退出的錯誤
                    if 600> resp.status_code >500 and self.numTry:
                        self.numTry -= 1
                        #遞歸 實現錯誤重試
                        return self.download(url,header,proxie)
            except requests.exceptions.RequestException as e:
                return {'html':None,'code':500}
            return {'html':html,'code':resp.status_code}

    resp 是訪問網頁響應實體,text屬性是網頁的文本內容,status_code 訪問網站狀態嗎,可以通過它來判斷訪問網頁是失敗,還是成功,以及是否需要重試,還是不需要重試的報錯;
    像下載網頁的具體內容,這種都是所有爬蟲都是一樣的,我們完全可以單獨寫一個類或者一個方法,以實現復用,這里我推薦,寫個回調類,方便我們后面擴展,以及存儲一些關鍵的信息,例如:緩存、代理、header
    在實現這個類,之前我們下介紹下緩存,代理的實現;

    代理
    代理實現起來比較簡單,request提供了很好的支持,只要給get()方法的proxies關鍵字參數,傳一個代理隊列就行resp = requests.get(url,headers=header,proxies=proxie,timeout=self.timeOut)

    緩存

    緩存需要重點介紹下,我們需要用到redis,redis的安裝教程,推薦到菜鳥驛站去學習;
    安裝號redis服務器后,還需要安裝python使用redis的模塊, 直接pip安裝就可以,pip install redis
    ,我們通過 url -> html文本的方式存儲我們訪問過的網頁,然后直接上代碼:

    import json
    import zlib
    from redis import StrictRedis
    from datetime import timedelta
    
    class RedisCache:
        #是否壓縮文件 compress endcoding 編碼方式,key:url value:html redis鏈接:client 設置緩存過期時間expires=timedelta(days=30)
        def __init__(self,client=None,compress=True,endcoding='utf-8',expires=timedelta(days=30)):
            self.client = StrictRedis(host='localhost',port=6379,db=0)
            self.compress = True
            self.endcoding =endcoding
            self.expires = expires
        #序列化 解壓 解碼 序列化
        def __getitem__(self,url):
            value = self.client.get(url)
            if value:
                if self.compress:
                    value = zlib.decompress(value)
                return json.loads(value.decode(self.endcoding))
            else:
                raise KeyError(url+'does exit')
        #反序列化 解碼 解壓
        def __setitem__(self,url,html):
            data = bytes(json.dumps(html),encoding=self.endcoding)
            if self.compress:
                data = zlib.compress(data)
                #設置過期時間 setex
            self.client.setex(url,self.expires,data)
    

    這里我們還通過zilb模塊對我們的代碼進行了壓縮,這是為了節省空間,因為redis是報內容存到磁盤中的,這里在說下這個self.client.setex(url,self.expires,data)方法,相比set方法,這個方法可以幫我們設置內容在數據庫中的有效時間;

    接下來實現下載類:

    import requests
    from redisCache import RedisCache
    from throttle import Throttle
    from random import choice
    class Downloader:
        #錯誤重復嘗試次數 numTry,延遲 delay 緩存 cache user_agent  proxies 代理
        def __init__(self,user_angent='wsap',proxies=None,delay=5,numTry=5,cache=None,timeout =30):
            self.user_agent=user_angent
            self.proxies = proxies
            self.delay =delay
            self.numTry=numTry
            self.cache = RedisCache()
            self.throt = Throttle(delay)
            self.timeOut =timeout
    
        #回調方法,可以讓類和方法一樣被使用
        def __call__(self,url):
            try:
                html = self.cache.__getitem__(url)
            except KeyError:
                html = None
            if html is None:
                self.throt.wait(url)
                header = {'user-agent':self.user_agent}
                #lamda表達式
                proixe = choice(self.proxies) if self.proxies else None
                html = self.download(url,header,proixe)
            self.cache.__setitem__(url,html)
            return html['html']
    
        #處理url下載問題
        def download(self,url,header,proxie):
            try:
                resp = requests.get(url,headers=header,proxies=proxie,timeout=self.timeOut)
                html = resp.text
                #小于400表示成功了
                if resp.status_code >=400:
                    html = None
                    #500到600需要重試 400到500是可以直接退出的錯誤
                    if 600> resp.status_code >500 and self.numTry:
                        self.numTry -= 1
                        #遞歸 實現錯誤重試
                        return self.download(url,header,proxie)
            except requests.exceptions.RequestException as e:
    
                return {'html':None,'code':500}
            return {'html':html,'code':resp.status_code}

    這里介紹下,回調類,回調類和普通類的區別是,他必須實現call()方法,實現這個方法后我們就可以像調用方法一樣調用我們的類,是不是很神奇(我也是接觸python之后才知道的類還可以這么用,哈哈);看代碼我們會發現 Throttle類,這個類干嘛的呢,他的主要目的是控制我們對同一個url訪問間隔的,因為我們都知道,大多數網站都是不希望被爬蟲光顧的,因為惡意的爬蟲和質量不高的爬蟲會造成服務器很大的壓力;所以有很多反爬措施,我們使用代理也正是這個原因,同樣這個類的目的也是樣的,具體實現主要是個一個字典;具體代碼如下:

    from urllib.parse import urlparse
    import time
    
    
    class Throttle:
        """ Add a delay between downloads to the same domain
        """
        def __init__(self, delay):
            # amount of delay between downloads for each domain
            self.delay = delay
            # timestamp of when a domain was last accessed
            self.domains = {}
    
        def wait(self, url):
            domain = urlparse(url).netloc
            last_accessed = self.domains.get(domain)
    
            if self.delay > 0 and last_accessed is not None:
                sleep_secs = self.delay - (time.time() - last_accessed)
                if sleep_secs > 0:
                    # domain has been accessed recently
                    # so need to sleep
                    time.sleep(sleep_secs)
            # update the last accessed time
            self.domains[domain] = time.time()
    

    到這里,一個善意的下載類已經寫好了;網頁內容都下下來了,怎么提取我們需要的內容呢;一個網頁包含內容課太多了,這就需要用到我們下面的內容了,分析網頁,抓取需要的數據;

    lxml 實現頁面數據的抓取

    其實,分析網頁的工具很多,最直接的就是我們的正則表達式了,這里還是推薦去菜鳥驛站,菜鳥驛站對于入門來說還是相當不錯了,但是我們這里不用正則表達式,因為正則式太復雜了,啦啦啦,我們用更簡單的工具,這就是大家愛python的原因?python為我們提供了很多處理html,xml的模塊;有beautifulsoup、css選擇器,xphath選擇器;這里我用的xpath,要使用我們xpath我們需要安裝lxml,還是一樣,通過pip install lxml;
    xphth 語法介紹:
    1. 選則所有鏈接 語法: //a
    2. 選擇類名為“main” div 元素 語法: //div[@class=”main”]
    3. 選擇ID為list 的ul 元素 語法: //ul[@id=”list”]
    4. 從所有段落中選擇文本 語法: //p/text()
    5. 選擇所有類名中包含test的div元素 語法: //div[contains(@class,’test’)]
    6.選擇所有包含鏈接和列表的div元素 語法://div[a|ul]

    來段代碼吧:

        def scrapy_callback(self,html):
            tree = fromstring(html)
            links = []
            title = tree.xpath('//div[@class="house-title LOGVIEWDATA LOGVIEW"]/div/text()') #獲取房子的信息
            price = tree.xpath('//span[@class="dealTotalPrice"]/i/text()') #獲取房子的價格

    //div[@class=”house-title LOGVIEWDATA LOGVIEW”],全局文本下的所有class 屬性 等于 house-title LOGVIEWDATA LOGVIEW div 標簽;
    /div,直接子節點下的所有div標簽 /text() 標簽的文本內容;
    對于初學者最迷惑的就是這個 // 和 / ,// 可以理解未所有子節點下面,/ 直接子節點;
    下面上完整的爬取代碼:

    import requests
    from downloader import Downloader
    from lxml.html import fromstring,tostring
    import json
    from multiprocessing import Process,queues
    import time
    import threading
    
    class scrapyProcess(Process):
        def __init__(self,region,q,agent,proxies,numThreads):
            #實現父類構造函數
            Process.__init__(self)
            #小區列表
            self.region = region
            #爬取url列表
            self.q = q
            self.agent =agent
            self.proxies =proxies
            #多線程個數
            self.numThreads = numThreads
        #爬取方法的入口
        def action(self):
            while self.q:
                D = Downloader(user_angent=self.agent, proxies=self.proxies)
                url = self.q.pop()
                html = D(url)
                if html:
                    totalpages = self.scrapy_page(html)
                    if totalpages:
                    #遍歷所有的網頁
                        for page in range(2,totalpages):
                            urlpage = self.starturl+'/'+"pg"+str(page)+"/"
                            if urlpage not in self.seen:
                                self.seen.add(urlpage)
                                self.q.append(urlpage)
                                htmlpage = D(urlpage)
                                links = self.scrapy_callback(htmlpage)
                                for linkurl in links:
                                    if linkurl not in self.seen:
                                        self.seen.add(linkurl)
                                        self.q.append(linkurl)
                    else:
                        print(url)
                        links = self.scrapy_callback(html)
                        for linkurl in links:
                            if linkurl not in self.seen:
                                self.seen.add(linkurl)
                                self.q.append(linkurl)
                else:
                    continue
    
    
        #獲取新的成交房源url并且存儲數據
        def scrapy_callback(self,html):
            tree = fromstring(html)
            links = []
            title = tree.xpath('//div[@class="house-title LOGVIEWDATA LOGVIEW"]/div/text()')
            price = tree.xpath('//span[@class="dealTotalPrice"]/i/text()')
    
        #插入數據庫房子的信息 偷懶,存在文件里,沒有再弄個數據庫
            try:
                print(title)
                with open('lianjia.txt','a') as f:
                    f.writelines(title + price)
            finally:
                if f:
                    f.close()
    
            link = tree.xpath('//a[@class="img"]/@href')
            link2= tree.xpath('//div[@class="fl pic"]/a/@href')
            if link:
                for li in link:
                    links.append(li)
            if link2:
                for li2 in link2:
                    links.append(li2)
            return links
        #構建爬取隊列
    #網頁上的內容是一個頁面無法顯示的,所有這里獲取網頁的頁數
        def scrapy_page(self, html):
            tree = fromstring(html)
            pagejson = tree.xpath('//div[@class="page-box house-lst-page-box"]/@page-data')
            totalpage=0
            if pagejson:
                pagejson = json.loads(pagejson[0])
                totalpage = pagejson["totalPage"]
            return totalpage

    這里為什么這么寫,需要結合鏈家網站來分析了,鏈家成交房源信息,就不帶大家分析了,可以通過分析下代碼,然后看看xpath所指向的節點應該就能明白代碼的邏輯了;這部分代碼大家也可以自己來實現啊,不一定非得和我這個一樣;沒啥特別的邏輯,就是通過xpath提取信息,然后通過一個set 去掉重復的url,避免重復提取同樣的內容;

    多線程和多進程提升爬取速度

    python 里的多線程其實效率并不是很高(這是因為python里一個全局鎖的概念,感興趣的可以自行百度),更多的是多進程,這里我們采用多進程和多線程結合的方式,一定數量的進程加上一定數量的線程,對速度的提升相當給力,大概8個進程5個線程吧,這個比例最好了,效果顯著;不要問我為啥是這個比例,我只說前人栽樹,后人乘涼;我們也可以結合代碼,調整比例驗證下;
    多線程 需要 import threading 多進程: from multiprocessing import Process,queues
    python 的進程和線程提供了兩種實現方式,一種是繼承進程或線程類,實現run方法,自定義自己的多進程,還有就是直接實例化python為我們提供的類,并指定進程或線程方法;我這的顯示思路是,下個自定義進程類,然后在類里開啟實例化線程,指定線程方法;所以我們需要對上面的類進行下改造;
    完整代碼如下:

    import requests
    from downloader import Downloader
    from lxml.html import fromstring,tostring
    import json
    from multiprocessing import Process,queues
    import time
    import threading
    
    class scrapyProcess(Process):
        def __init__(self,region,q,agent,proxies,numThreads):
            #實現父類構造函數
            Process.__init__(self)
            #小區列表
            self.region = region
            #爬取url列表
            self.q = q
            self.agent =agent
            self.proxies =proxies
            #多線程個數
            self.numThreads = numThreads
    
        def run(self):
            self.starturl = 'https://nj.lianjia.com/chengjiao/' + self.region+'/'
            #需要用到共有資源需要枷鎖
            self.lock = threading.RLock()
            #通過set控制不要重復爬取數據
            self.seen = set()
            self.q.append(self.starturl)
            threads = []
            #開啟線程
            for th in range(self.numThreads):
                thread = threading.Thread(target=self.action())
                thread.start()
                threads.append(thread)
            #線程主線程必須等到子線程關閉
            for thj in threads:
                thj.join()
        def action(self):
            while self.q:
                self.lock.acquire()
                #這個需要插入數據庫,保證數據的唯一,需要放到線程里面
                D = Downloader(user_angent=self.agent, proxies=self.proxies)
                url = self.q.pop()
                html = D(url)
                if html:
                    totalpages = self.scrapy_page(html)
                    if totalpages:
                        for page in range(2,totalpages):
                            urlpage = self.starturl+'/'+"pg"+str(page)+"/"
                            if urlpage not in self.seen:
                                self.seen.add(urlpage)
                                self.q.append(urlpage)
                                htmlpage = D(urlpage)
                                links = self.scrapy_callback(htmlpage)
                                for linkurl in links:
                                    if linkurl not in self.seen:
                                        self.seen.add(linkurl)
                                        self.q.append(linkurl)
                    else:
                        print(url)
                        links = self.scrapy_callback(html)
                        for linkurl in links:
                            if linkurl not in self.seen:
                                self.seen.add(linkurl)
                                self.q.append(linkurl)
                    self.lock.release()
                else:
                    self.lock.release()
                    continue
    
    
        #獲取新的成交房源url并且存儲數據
        def scrapy_callback(self,html):
            tree = fromstring(html)
            links = []
            title = tree.xpath('//div[@class="house-title LOGVIEWDATA LOGVIEW"]/div/text()')
            price = tree.xpath('//span[@class="dealTotalPrice"]/i/text()')
    
        #插入數據庫房子的信息
            try:
                print(title)
                with open('lianjia.txt','a') as f:
                    f.writelines(title + price)
            finally:
                if f:
                    f.close()
    
            link = tree.xpath('//a[@class="img"]/@href')
            link2= tree.xpath('//div[@class="fl pic"]/a/@href')
            if link:
                for li in link:
                    links.append(li)
            if link2:
                for li2 in link2:
                    links.append(li2)
            return links
        #構建爬取隊列
    
        def scrapy_page(self, html):
            tree = fromstring(html)
            pagejson = tree.xpath('//div[@class="page-box house-lst-page-box"]/@page-data')
            totalpage=0
            if pagejson:
                pagejson = json.loads(pagejson[0])
                totalpage = pagejson["totalPage"]
            return totalpage
    

    這里我給進程加了個鎖的屬性lock, 這是因為我們進程類,會開啟多個線程(根據我們進程的numThreads),他們共享我們進程的所有變量,為了保證進程不出現數據混亂,所以需要加入鎖;
    最后,是我們的啟動代碼了:

    if __name__ == '__main__':
        #多進程
        q = []
        #regions 列表的長度就是我們進程的個數;
        regions = ["gulou","jianye"]
        process_list = []
        numTh = 5
        #記錄下開始時間
        start = time.time()
        for region in regions:
            scrapySpider = scrapyProcess(region,q,"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0",None,numTh)
            scrapySpider.start()
            process_list.append(scrapySpider)
        #主進程必須等到子線程關閉
        for pro in process_list:
            pro.join()
        #打印實際耗時
        print ("耗時:%s"%time.time()-start)

    我們可以通過設置regions的長度和numTh 來設置不同的進程和線程比例,并通過耗時,來驗證我們比例了;

    暫告一段落;下一篇我們為爬蟲塔上分布式的順風車 ,哈哈哈哈哈哈!有錯誤希望打家幫忙指出,第一次嘗試寫完整的博客

    最后是完整的代碼地址:
    github地址

    最后的最后

    這邊博客是學習python的一個總結,爬取網頁和緩存借鑒了《用python寫網絡爬蟲》這本書,也把這本書推薦個大家;對我的幫助挺大的

    版權聲明:本文為caca95原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/caca95/article/details/82217507

    智能推薦

    python爬蟲獲取鏈家二手房源信息

    0X00 前言 本來之前是準備爬取boss直聘的招聘信息,結果boss的反爬還挺惡心,訪問頁面還得帶上cookie,頁面的cookie有效時間也只有一分鐘,不然只能訪問到等待頁面,菜雞落淚 0X01 準備工作 使用到的標準庫lxml、requests、re、requests.exceptions、os和openpyxl、random、time。 0X02 分析 相比較boss來說,鏈家的網站對于爬...

    python爬取鏈家租房信息

    本人是上海某211高校研二在讀理工男一枚 臨近工作,最近爬取了鏈家上海的二手房,新房,租房以及小區信息 完整代碼與csv文件可以從我的GitHub地址獲取 PS:歡迎star+fork+follow一箭三連 GitHub地址 具體流程就不多說了 爬蟲就是那樣,請求url,獲得網頁源代碼,提取信息,保存信息 直接上代碼 爬取后本地文件夾里面變多了LJzufang.csv這個文件 打開看看效果 展示了...

    神奇的Batch Normalization 如果一個模型僅訓練BN層會是什么樣的

    您可能會感到驚訝,但這是有效的。 ? 最近,我閱讀了arXiv平臺上的Jonathan Frankle,David J. Schwab和Ari S. Morcos撰寫的論文“Training BatchNorm and Only BatchNorm: On the Expressive Power of Random Features in CNNs”。 這個主意立刻引起了...

    用Python實現校園通知更新提醒

    前言 這個項目實已經在一個月前已經完成了,一直都想寫一篇博客來總結這個過程中遇到的一些問題。但最近一個月來都比較忙,所以一直拖到了現在。 首先說說起因吧,我沒事的時候,總喜歡依次點開學校主頁、教務處、圖書館以及學院的網站,看看有沒有什么新通知,雖然大多與我無關。恰逢最近正在學Python,經常聽到別人說用Python寫爬蟲很簡單,但自己尚未接觸過爬蟲。于是抱著試一試的心態看了幾篇關于Python爬...

    spring_ioc相關_第一章

    1 spring是一站式框架,在javaee的三層結構中,每一層都提供不提并的解決技術 web層:springMVC service層:spring的ioc dao層:spring的jdbcTemplate 2 javaee為避免兩個類之間出現耦合,則把對象的創建交給spring進行管理,spring的ioc操作:(1)ioc的配置文件方式;(2)ioc注解方式 3 ioc的底層原理使用技術(1)...

    猜你喜歡

    【Python+OpenCV】視頻流局部區域像素值處理-一種特征提取方法

    參考我之前寫的處理圖片的文章:Python+OpenCV實現【圖片】局部區域像素值處理(改進版) 開發環境:Python3.6.0 + OpenCV3.2.0 任務目標:攝像頭采集圖像(例如:480*640),并對視頻流每一幀(灰度圖)特定矩形區域(480*30)像素值進行行求和,得到一個480*1的數組,用這480個數據繪制條形圖,即在逐幀采集視頻流并處理后“實時”顯示采...

    JavaWeb——【前端】——注冊頁面

    頁面效果 實現代碼 注意事項 主要使用的bootstrap樣式 如果想引用,不要直接復制,沒用的。 先介紹下所引用的文件: boostrap的js、bootstrap的css、jquery的js、以及自己編寫的register.css。 因為博主用的thymeleaf語法,所以有th符號。 若要使用時,根據個人情況導入相應的依賴。...

    網站HTTP升級HTTPS完全配置手冊

    本文由葡萄城技術團隊于博客園原創并首發 轉載請注明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。 今天,所有使用Google Chrome穩定版的用戶迎來了v68正式版首個版本的發布,詳細版本號為v68.0.3440.75,上一個正式版v67.0.3396.99發布于6月13日,自Chrome 68起,當在加載非HTTPS站點時,都會在地址欄上明確標記為&ldqu...

    echarts 自定義儀表盤設置背景圖片

    echarts儀表盤 使用插件 vue-echarts 代碼示例 HTML部分 js部分 效果圖...

    RT-Thread Studio部分定時器時鐘不正確的解決方案

    在昨天的RT-Thread Studio硬件定時器hwtimer在stm32f411上的使用筆記中,遇到了部分定時器速度想象中和實際不一致的情況,具體表現在定時器2、3、4、5、9、10、11都正常,但定時器1要快一倍。 仔細查看代碼,找到了原因。 因為代碼使用的是工程是直接生成的時鐘代碼,實際的時鐘頻率是這樣的: 而實際的定時器時鐘配置代碼如下: 針對F411,去掉其中的宏定義是這樣的: 這里說...

    精品国产乱码久久久久久蜜桃不卡