<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          送書|獲取旅游景點信息及評論并作詞云、數(shù)據(jù)可視化

          共 9525字,需瀏覽 20分鐘

           ·

          2021-10-14 00:58


          大家好,我是啃書君!

          正所謂:有朋自遠方來,不亦樂乎?有朋友來找我們玩,是一件很快樂的事情,那么我們要盡地主之誼,好好帶朋友去玩耍!那么問題來了,什么時候去哪里玩最好呢,哪里玩的地方最多呢?

          今天將手把手教你使用線程池爬取同程旅行的景點信息及評論數(shù)據(jù)并做詞云、數(shù)據(jù)可視化?。?!帶你了解各個城市的游玩景點信息。

          在開始爬取數(shù)據(jù)之前,我們首先來了解一下線程。

          線程

          進程:進程是代碼在數(shù)據(jù)集合上的一次運行活動,是系統(tǒng)進行資源分配和調(diào)度的基本單位。

          線程:是輕量級的進程,是程序執(zhí)行的最小單元,是進程的一個執(zhí)行路徑。

          一個進程中至少有一個線程,進程中的多個線程共享進程的資源。

          線程生命周期

          在創(chuàng)建多線程之前,我們先來學習一下線程生命周期,如下圖所示:

          由圖可知,線程可以分為五個狀態(tài)——新建、就緒、運行、阻塞、終止。

          首先新建一個線程并開啟線程后線程進入就緒狀態(tài),就緒狀態(tài)的線程不會馬上運行,要獲得CPU資源才會進入運行狀態(tài),在進入運行狀態(tài)后,線程有可能會失去CPU資源或者遇到休眠、io操作(讀寫等操作)線程進入就緒狀態(tài)或者阻塞狀態(tài),要等休眠、io操作結束或者重新獲得CPU資源后,才會進入運行狀態(tài),等到運行完后進入終止狀態(tài)。

          注意:新建線程系統(tǒng)是需要分配資源的,終止線程系統(tǒng)是需要回收資源的,那么如何減去新建/終止線程的系統(tǒng)開銷呢,這時我們可以創(chuàng)建線程池來重用線程,這樣就可以減少系統(tǒng)的開銷了。

          在創(chuàng)建線程池之前,我們先來學習如何創(chuàng)建多線程。

          創(chuàng)建多線程

          創(chuàng)建多線程可以分為四步:

          1. 創(chuàng)建函數(shù);
          2. 創(chuàng)建線程;
          3. 啟動線程;
          4. 等待結束;

          創(chuàng)建函數(shù)

          為了方便演示,我們拿博客園的網(wǎng)頁做爬蟲函數(shù),具體代碼如下所示:

          import?requests
          urls=[
          ????f'https://www.cnblogs.com/#p{page}'
          ????for?page?in?range(1,50)
          ]
          def?get_parse(url):
          ????response=requests.get(url)
          ????print(url,len(response.text))

          首先導入requests網(wǎng)絡請求庫,把我們所有的要爬取的URL保存在列表中,然后自定義函數(shù)get_parse來發(fā)送網(wǎng)絡請求、打印請求的URL和響應的字符長度。

          創(chuàng)建線程

          在上一步我們創(chuàng)建了爬蟲函數(shù),接下來將創(chuàng)建線程了,具體代碼如下所示:

          import?threading
          #多線程
          def?multi_thread():
          ????threads=[]
          ????for?url?in?urls:
          ????????threads.append(
          ????????????threading.Thread(target=get_parse,args=(url,))
          ????????)

          首先我們導入threading模塊,自定義multi_thread函數(shù),再創(chuàng)建一個空列表threads來存放線程任務,通過threading.Thread()方法來創(chuàng)建線程。其中:

          • target為運行函數(shù);
          • args為運行函數(shù)所需的參數(shù)。

          注意args中的參數(shù)要以元組的方式傳入,然后通過.append()方法把線程添加到threads空列表中。

          啟動線程

          線程已經(jīng)創(chuàng)建好了,接下來將啟動線程了,啟動線程很簡單,具體代碼如下所示:

          for?thread?in?threads:
          ????thread.start()

          首先我們通過for循環(huán)把threads列表中的線程任務獲取下來,通過.start()來啟動線程。

          等待結束

          啟動線程后,接下來將等待線程結束,具體代碼如下所示:

          for?thread?in?threads:
          ????thread.join()

          和啟動線程一樣,先通過for循環(huán)把threads列表中的線程任務獲取下來,再使用.join()方法等待線程結束。

          多線程已經(jīng)創(chuàng)建好了,接下來將測試一下多線程的速度如何,具體代碼如下所示:

          if?__name__?==?'__main__':
          ?t1=time.time()
          ????multi_thread()
          ????t2=time.time()
          ????print(t2-t1)

          運行結果如下圖所示:

          多線程爬取50個博客園網(wǎng)頁只要1秒多,而且多線程的發(fā)送網(wǎng)絡請求的URL是隨機的。

          我們來測試一下單線程的運行時間,具體代碼如下所示:

          if?__name__?==?'__main__':
          ????t1=time.time()
          ????for?i?in?urls:
          ????????get_parse(i)
          ????t2=time.time()
          ????print(t2-t1)

          運行結果如下圖所示:

          單線程爬取50個博客園網(wǎng)頁用了9秒多,單線程的發(fā)送網(wǎng)絡請求的URL是按順序的。

          在上面我們說了,新建線程系統(tǒng)是需要分配資源的,終止線程系統(tǒng)是需要回收資源的,為了減少系統(tǒng)的開銷,我們可以創(chuàng)建線程池。

          線程池原理

          一個線程池由兩部分組成,如下圖所示:

          • 線程池:里面提前建好N個線程,這些都會被重復利用;
          • 任務隊列:當有新任務的時候,會把任務放在任務隊列中。

          當任務隊列里有任務時,線程池的線程會從任務隊列中取出任務并執(zhí)行,執(zhí)行完任務后,線程會執(zhí)行下一個任務,直到?jīng)]有任務執(zhí)行后,線程會回到線程池中等待任務。

          使用線程池可以處理突發(fā)性大量請求或需要大量線程完成任務(處理時間較短的任務)。

          好了,了解了線程池原理后,我們開始創(chuàng)建線程池。

          線程池創(chuàng)建

          Python提供了ThreadPoolExecutor類來創(chuàng)建線程池,其語法如下所示:

          ThreadPoolExecutor(max_workers=None,?thread_name_prefix='',?initializer=None,?initargs=())

          其中:

          • max_workers:最大線程數(shù);
          • thread_name_prefix:允許用戶控制由線程池創(chuàng)建的threading.Thread工作線程名稱以方便調(diào)試;
          • initializer:是在每個工作者線程開始處調(diào)用的一個可選可調(diào)用對象;
          • initargs:傳遞給初始化器的元組參數(shù)。

          注意:在啟動 max_workers 個工作線程之前也會重用空閑的工作線程。

          在ThreadPoolExecutor類中提供了map()和submit()函數(shù)來插入任務隊列。其中:

          map()函數(shù)

          map()語法格式為:

          map(調(diào)用方法,參數(shù)隊列)

          具體示例如下所示:

          import?requestsimport?concurrent.futuresimport?timeurls=[????f'https://www.cnblogs.com/#p{page}'????for?page?in?range(1,50)]def?get_parse(url):????response=requests.get(url)????return?response.textdef?map_pool():????with?concurrent.futures.ThreadPoolExecutor(max_workers=20)?as?pool:????????htmls=pool.map(get_parse,urls)????????htmls=list(zip(urls,htmls))????????for?url,html?in?htmls:????????????print(url,len(html))if?__name__?==?'__main__':????t1=time.time()????map_pool()????t2=time.time()????print(t2-t1)

          首先我們導入requests網(wǎng)絡請求庫、concurrent.futures模塊,把所有的URL放在urls列表中,然后自定義get_parse()方法來返回網(wǎng)絡請求返回的數(shù)據(jù),再自定義map_pool()方法來創(chuàng)建代理池,其中代理池的最大max_workers為20,調(diào)用map()方法把網(wǎng)絡請求任務放在任務隊列中,在把返回的數(shù)據(jù)和URL合并為元組,并放在htmls列表中。

          運行結果如下圖所示:

          可以發(fā)現(xiàn)map()函數(shù)返回的結果和傳入的參數(shù)順序是對應的。

          注意:當我們直接在自定義方法get_parse()中打印結果時,打印結果是亂序的。

          submit()函數(shù)

          submit()函數(shù)語法格式如下:

          submit(調(diào)用方法,參數(shù))

          具體示例如下:

          def?submit_pool():????with?concurrent.futures.ThreadPoolExecutor(max_workers=20)as?pool:????????futuress=[pool.submit(get_parse,url)for?url?in?urls]????????futures=zip(urls,futuress)????????for?url,future?in?futures:????????????print(url,len(future.result()))

          運行結果如下圖所示:

          注意:submit()函數(shù)輸出結果需需要調(diào)用result()方法。

          好了,線程知識就學到這里了,接下來開始我們的爬蟲。

          爬前分析

          首先我們進入同程旅行的景點網(wǎng)頁并打開開發(fā)者工具,如下圖所示:

          經(jīng)過尋找,我們發(fā)現(xiàn)各個景點的基礎信息(詳情頁URL、景點id等)都存放在下圖的URL鏈接中,

          其URL鏈接為:

          https://www.ly.com/scenery/NewSearchList.aspx?&action=getlist&page=2&kw=&pid=6&cid=80&cyid=0&sort=&isnow=0&spType=&lbtypes=&IsNJL=0&classify=0&grade=&dctrack=1%CB%871629537670551030%CB%8720%CB%873%CB%872557287248299209%CB%870&iid=0.6901326566387387

          經(jīng)過增刪改查操作,我們可以把該URL簡化為:

          https://www.ly.com/scenery/NewSearchList.aspx?&action=getlist&page=1&pid=6&cid=80&cyid=0&isnow=0&IsNJL=0

          其中page為我們翻頁的重要參數(shù)。

          打開該URL鏈接,如下圖所示:

          通過上面的URL鏈接,我們可以獲取到很多景點的基礎信息,隨機打開一個景點的詳情網(wǎng)頁并打開開發(fā)者模式,經(jīng)過查找,評論數(shù)據(jù)存放在如下圖的URL鏈接中,

          其URL鏈接如下所示:

          https://www.ly.com/scenery/AjaxHelper/DianPingAjax.aspx?action=GetDianPingList&sid=12851&page=1&pageSize=10&labId=1&sort=0&iid=0.48901069375088

          其中:action、labId、iid、sort為常量,sid是景點的id,page控制翻頁,pageSize是每頁獲取的數(shù)據(jù)量。

          在上上步中,我們知道景點id的存放位置,那么構造評論數(shù)據(jù)的URL就很簡單了。

          實戰(zhàn)演練

          這次我們爬蟲步驟是:

          1. 獲取景點基本信息
          2. 獲取評論數(shù)據(jù)
          3. 創(chuàng)建MySQL數(shù)據(jù)庫
          4. 保存數(shù)據(jù)
          5. 創(chuàng)建線程池
          6. 數(shù)據(jù)分析

          獲取景點基本信息

          首先我們先獲取景點的名字、id、價格、特色、地點和等級,主要代碼如下所示:

          def?get_parse(url):????response=requests.get(url,headers=headers)????Xpath=parsel.Selector(response.text)????data=Xpath.xpath('/html/body/div')????for?i?in?data:????????Scenery_data={????????????'title':i.xpath('./div/div[1]/div[1]/dl/dt/a/text()').extract_first(),????????????'sid':i.xpath('//div[@class="list_l"]/div/@sid').extract_first(),????????????'Grade':i.xpath('./div/div[1]/div[1]/dl/dd[1]/span/text()').extract_first(),???????'Detailed_address':i.xpath('./div/div[1]/div[1]/dl/dd[2]/p/text()').extract_first().replace('地址:',''),????????????'characteristic':i.xpath('./div/div[1]/div[1]/dl/dd[3]/p/text()').extract_first(),????????????'price':i.xpath('./div/div[1]/div[2]/div[1]/span/b/text()').extract_first(),????????????'place':i.xpath('./div/div[1]/div[1]/dl/dd[2]/p/text()').extract_first().replace('地址:','')[6:8]????????}

          首先自定義方法get_parse()來發(fā)送網(wǎng)絡請求后使用parsel.Selector()方法來解析響應的文本數(shù)據(jù),然后通過xpath來獲取數(shù)據(jù)。

          獲取評論數(shù)據(jù)

          獲取景點基本信息后,接下來通過景點基本信息中的sid來構造評論信息的URL鏈接,主要代碼如下所示:

          def?get_data(Scenery_data):????for?i?in?range(1,3):????????link?=?f'https://www.ly.com/scenery/AjaxHelper/DianPingAjax.aspx?action=GetDianPingList&sid={Scenery_data["sid"]}&page={i}&pageSize=100&labId=1&sort=0&iid=0.20105777381446832'????????response=requests.get(link,headers=headers)????????Json=response.json()????????commtent_detailed=Json.get('dpList')????????#?有評論數(shù)據(jù)????????if?commtent_detailed!=None:????????????for?i?in?commtent_detailed:????????????????Comment_information={????????????????????'dptitle':Scenery_data['title'],????????????????????'dpContent':i.get('dpContent'),????????????????????'dpDate':i.get('dpDate')[5:7],????????????????????'lineAccess':i.get('lineAccess')????????????????}????????#沒有評論數(shù)據(jù)????????elif?commtent_detailed==None:????????????Comment_information={????????????????'dptitle':Scenery_data['title'],????????????????'dpContent':'沒有評論',????????????????'dpDate':'沒有評論',????????????????'lineAccess':'沒有評論'????????????}

          首先自定義方法get_data()并傳入剛才獲取的景點基礎信息數(shù)據(jù),然后通過景點基礎信息的sid來構造評論數(shù)據(jù)的URL鏈接,當在構造評論數(shù)據(jù)的URL時,需要設置pageSize和page這兩個變量來獲取多條評論和進行翻頁,構造URL鏈接后就發(fā)送網(wǎng)絡請求。

          這里需要注意的是:有些景點是沒有評論,所以我們需要通過if語句來進行設置。

          創(chuàng)建MySQL數(shù)據(jù)庫

          這次我們把數(shù)據(jù)存放在MySQL數(shù)據(jù)庫中,由于數(shù)據(jù)比較多,所以我們把數(shù)據(jù)分為兩種數(shù)據(jù)表,一種是景點基礎信息表,一種是景點評論數(shù)據(jù)表,主要代碼如下所示:

          #創(chuàng)建數(shù)據(jù)庫def?create_db():????db=pymysql.connect(host=host,user=user,passwd=passwd,port=port)????cursor=db.cursor()????sql='create?database?if?not?exists?commtent?default?character?set?utf8'????cursor.execute(sql)????db.close()????create_table()#創(chuàng)建景點信息數(shù)據(jù)表def?create_table():????db=pymysql.connect(host=host,user=user,passwd=passwd,port=port,db='commtent')????cursor=db.cursor()????sql?=?'create?table?if?not?exists?Scenic_spot_data?(title?varchar(255)?not?null,?link?varchar(255)?not?null,Grade?varchar(255)?not?null,?Detailed_address?varchar(255)?not?null,?characteristic?varchar(255)not?null,?price?int?not?null,?place?varchar(255)?not?null)'????cursor.execute(sql)????db.close()

          首先我們調(diào)用pymysql.connect()方法來連接數(shù)據(jù)庫,通過.cursor()獲取游標,再通過.execute()方法執(zhí)行單條的sql語句,執(zhí)行成功后返回受影響的行數(shù),然后關閉數(shù)據(jù)庫連接,最后調(diào)用自定義方法create_table()來創(chuàng)建景點信息數(shù)據(jù)表。

          這里我們只給出了創(chuàng)建景點信息數(shù)據(jù)表的代碼,因為創(chuàng)建數(shù)據(jù)表只是sql這條語句稍微有點不同,其他都一樣,大家可以參考這代碼來創(chuàng)建各個景點評論數(shù)據(jù)表。

          保存數(shù)據(jù)

          創(chuàng)建好數(shù)據(jù)庫和數(shù)據(jù)表后,接下來就要保存數(shù)據(jù)了,主要代碼如下所示:

          #保存景點數(shù)據(jù)到景點數(shù)據(jù)表中def?saving_scenery_data(srr):????db?=?pymysql.connect(host=host,?user=user,?password=passwd,?port=port,?db='commtent')????cursor?=?db.cursor()????sql?=?'insert?into?Scenic_spot_data(title,?link,?Grade,?Detailed_address,?characteristic,price,place)?values(%s,%s,%s,%s,%s,%s,%s)'????try:????????cursor.execute(sql,?srr)????????db.commit()????except:????????db.rollback()????db.close()

          首先我們調(diào)用pymysql.connect()方法來連接數(shù)據(jù)庫,通過.cursor()獲取游標,再通過.execute()方法執(zhí)行單條的sql語句,執(zhí)行成功后返回受影響的行數(shù),使用了try-except語句,當保存的數(shù)據(jù)不成功,就調(diào)用rollback()方法,撤消當前事務中所做的所有更改,并釋放此連接對象當前使用的任何數(shù)據(jù)庫鎖。

          注意:srr是傳入的景點信息數(shù)據(jù)。

          創(chuàng)建線程池

          好了,單線程爬蟲已經(jīng)寫好了,接下來將創(chuàng)建一個函數(shù)來創(chuàng)建我們的線程池,使單線程爬蟲變?yōu)槎嗑€程,主要代碼如下所示:

          urls?=?[????f'https://www.ly.com/scenery/NewSearchList.aspx?&action=getlist&page={i}&pid=6&cid=80&cyid=0&isnow=0&IsNJL=0'????for?i?in?range(1,?6)]def?multi_thread():????with?concurrent.futures.ThreadPoolExecutor(max_workers=8)as?pool:????????h=pool.map(get_parse,urls)if?__name__?==?'__main__':????create_db()????multi_thread()

          創(chuàng)建線程池的代碼很簡單就一個with語句和調(diào)用map()方法

          運行結果如下圖所示:

          好了,數(shù)據(jù)已經(jīng)獲取到了,接下來將進行數(shù)據(jù)分析。

          數(shù)據(jù)可視化

          首先我們來分析一下各個景點那個月份游玩的人數(shù)最多,這樣我們就不用擔心去游玩的時機不對了。

          我們發(fā)現(xiàn)10月、2月、1月去廣州長隆飛鳥樂園游玩的人數(shù)占總體比例最多。分析完月份后,我們來看看評論情況如何:

          可以發(fā)現(xiàn)去好評占了絕大部分,可以說:去長隆飛鳥樂園玩耍,去了都說好??戳嗽u論情況,評論內(nèi)容有什么:

          好了,獲取旅游景點信息及評論并做詞云、數(shù)據(jù)可視化就講到這里了。

          送書

          又到了每周三的送書時刻,今天給大家?guī)淼氖恰?span style=" outline: 0px; font-weight: 700;letter-spacing: 0.544px; ">Python網(wǎng)絡爬蟲案例實戰(zhàn)》,《Python網(wǎng)絡爬蟲案例實戰(zhàn)》介紹如何利用Python開發(fā)網(wǎng)絡爬蟲,實用性較強?!禤ython網(wǎng)絡爬蟲案例實戰(zhàn)》以案例項目為驅(qū)動,由淺入深地講解爬蟲開發(fā)中所需要的知識和技能。從靜態(tài)網(wǎng)站到動態(tài)網(wǎng)站,從單機爬蟲到分布式爬蟲,既包含基礎知識點,又講解了關鍵問題和重難點問題,包含從入門到進階的所有知識?!禤ython網(wǎng)絡爬蟲案例實戰(zhàn)》主要包括爬蟲網(wǎng)絡概述、Web前端、靜態(tài)網(wǎng)絡抓取、動態(tài)網(wǎng)頁抓取、解析網(wǎng)頁、Python并發(fā)、數(shù)據(jù)庫、反爬蟲、亂碼問題、登錄與驗證碼、采集服務器、基礎爬蟲、App爬取、分布式爬蟲、爬蟲的綜合實戰(zhàn)等內(nèi)容。《Python網(wǎng)絡爬蟲案例實戰(zhàn)》適合Python初學者,也適合研究Python的廣大科研人員、學者、工程技術人員。




          公眾號回復:送書?,參與抽獎(共3本)

          點擊下方回復:送書? 即可!



          大家如果有什么建議,歡迎掃一掃二維碼私聊小編~
          回復:加群?可加入Python技術交流群


          瀏覽 75
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  玖玖精品在线 | 亚洲 欧美 国产 日韩 动漫第一页 | 亚洲色图欧美在线 | 亚洲毛片在线 | 久久在线黄色视频 |