携程旅游网与马蜂窝游客记录爬取
前言:這是我學習Python爬蟲以來,第一次使用python進行大規模的進行數據挖掘。邀請我加入她們科研項目的是工商學院的彭老師,做一個關于避暑旅游的課題。當他們需要獲取攜程旅游筆記時,由于文章的內容太多,思路也沒有考慮好,無法使用數據采集器把筆記放入Excel。于是找到了我們信息學院尋求方法幫助,我的輔導員老師就向她推薦了我。終于有機會進行大型的項目實戰了,非常幸運能參與這次的項目。但也由于平時比較繁忙,真正開發的時間并不是很多,通常都是課余時間做的。
第一次項目交接
她安排她的學生黃超來與我進行交接,告訴我,他們的需求。以前數據的采集就是他負責的,所以需求還是非常清楚的。他打電話與我溝通,說需要把旅游的筆記放進Excel做數據分析,我不是很理解,對于這些不規則的文本,最好的方式應該是放進txt文本才方便呀,經過一些溝通后,確定第二天見面談。我需要在周末上雙學位的課程,10月13號下午下課后就在我們上課的教室討論,指出我需要爬取的內容。
當天也相當于交流學習,我從未做過這方面的實戰。聽他的要求后,最理想的方式是把它文字保存在txt文本,并不是Excel,初步達成共識。
第一次爬取:攜程網,游客游記,關鍵詞“避暑旅游”,不限時間,初次搜索出4398條
由于數量太大,更改關鍵詞為“避暑”,時間為1年內,游記,共找到800篇,作為這次爬取的目標。由于是初次做這樣的工作,變弄便溝通,花了3多小時的時間才完成初步的工作,實現思路如下:
實現思路第一步:爬取一篇文本做測試
1.1.1、獲取單篇文章游客筆記文本
通過分析HTML發現,我所需要獲取的文字都在class="ctd_content"這個標簽類,直接通過BeautifulSoup的find模塊找到所有的文本就可以了;
運行結果如下:
1.1.2、保存獲取的內容到txt文本
txt = str(txt)#把獲取的內容轉換為字符,建議使用 filename = 'D:write_data.txt' with open(filename,'w',encoding='utf-8') as f: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!f.write(txt)f.close()txt截屏:
經驗:為什么要把獲取到的內容轉化為字符串?
我在寫入txt的過程中,發現很多文本會提示類型有有誤,轉成字符串后就沒有報錯了。
現在算是成功最主要的一步了,接下來就是爬取所有的文章了。
實現思路第二步:獲取所有文本鏈接
1.2.1、獲取一個主頁的所有文章鏈接
import requests from lxml import etree url = 'https://you.ctrip.com/searchsite/travels/?query=避暑&isAnswered=&isRecommended=&publishDate=365&PageNo=1' html = requests.get(url).text txt = etree.HTML(html) file = txt.xpath('/html/body/div[2]/div[2]/div[2]/div/div[1]/ul/li') for t in file:href = t.xpath('./dl/dt/a/@href')[0]hrefUrl = 'https://you.ctrip.com'+href#把鏈接補充完整print (hrefUrl)獲取結果:
注意: 攜程網使用的是相對路徑,我們需要把獲取的鏈接補充完整,在前面加上“https://you.ctrip.com”,
1.2.1、獲取每個主頁的鏈接
1.2.1、獲取每個主頁的所有文章鏈接,并添加到數組
為什么要把獲取到的鏈接存在數組,不是直接進行獲取它的內容?
1、從數組內遍歷出來速度比單獨爬取一個會更快
2、減少被反爬的概率,經常訪問更容易被識別出爬蟲
3、優化邏輯
1.2.1、開始獲取所有的文章
import requests from lxml import etree from bs4 import BeautifulSoupheaders = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' } k = 0 allUrl = [] for num in range(1,81,1):print(num)url = 'https://you.ctrip.com/searchsite/travels/?query=避暑&isAnswered=&isRecommended=&publishDate=365&PageNo='+str(num)html = requests.get(url).texttxt = etree.HTML(html)file = txt.xpath('/html/body/div[2]/div[2]/div[2]/div/div[1]/ul/li')for t in file:href = t.xpath('./dl/dt/a/@href')[0]hrefUrl = 'https://you.ctrip.com'+hrefallUrl.append(hrefUrl)for useUrl in allUrl:k = k + 1print ("正在獲取第%s篇"%k)print (useUrl)html = requests.get(url = useUrl,headers=headers).textsoup = BeautifulSoup(html, "html.parser")t = soup.find(attrs={"class": "ctd_content"})txt = t.get_text().replace("\n","")txt = str(txt)filename = 'G:write_data.txt'with open(filename,'a',encoding='utf-8') as f: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!f.write(txt)f.close() print ("獲取完畢!")幸運點:
1、還好這次的運氣比較好,沒有遇見服務器請求失敗的情況,一氣呵成
2、網頁結構統一,沒有出現結構異常而報錯
更具他留下來的要求,還需要爬取攜程網的問答模塊,馬蜂窩的三個模塊,分別是關鍵詞為“避暑”的游記,攻略和問答。
第二天,我在上課的時間完成了對攜程網問答模塊的爬取,并把完成的文本發給他先做研究。晚上的時候,他告訴我說,他們老師想見我,和我仔細的交流一下。
第二次項目交接
周二,我白天要上班到16:30,我晚上還要上三節課,通過電話約定16:40去找她,簡單的交流一下。這次說是項目交接,還不如說叫做聊天吧,彭老師告訴我她們在做什么項目,并且完成的進度,對技術人員的需求。接下來我就是向她展示了一下我爬取數據的思路,方便她在寫文稿的時候有相關的思路,并且簡單的確認了一下接下來數據的獲取。
在她看來,會一門別人不會的技術,在做學術上是有很大優勢的。而我所掌握的網絡爬蟲就是一門不錯的技術,希望我能做好這門技術,給講了很多考研和做學術的事。多參與這類項目的開發,并發表相關的論文,對我考研是有很大幫助的,但對于考研的事情,我并沒有明確自己的目標。
經過交談,她也想學習Python爬蟲,并讓我有時間的時候指導一下學習,后來幫她安裝好了學習Python的編譯,給了一些入門基礎教程,讓她先了解一下基礎,以后再教授爬蟲實戰和對付反爬等相關工作。
當天晚上,我開始對馬蜂窩的數據進行了爬取實戰。
最近有人使用數據采集器爬了馬蜂窩的1800萬數據就刷爆了網絡,驚動了互聯網界和投資界,背后的數據團隊也因此爆紅。所以它的數據又重新做好保護措施,我這次爬取它還是花了一些時間來研究它的。請求到它的源碼后,發現我需要的數據都沒有在其中,這就有點麻煩了,只能使用其他的辦法了。經過仔細的查看后,我發現它的數據全部都是動態加載的,你看到哪里,它就加載到哪里,建立這個思維后,我開始使用selenium搞自動化,但是這個的效率就要慢很多了,還好不負努力,終于把它解決了,花了一節課的時間運行代碼,爬取了所有的游記,共計46篇,速度已經慢得難受了,還好是上課時間,聽了一節課,不影響我時間的使用。
2.1.1、獲取主頁的所有文章鏈接,保存到數組
import requests from lxml import etree from bs4 import BeautifulSoup filename = 'G:馬蜂窩游記筆記文本.txt' noteBook = 'G:馬蜂窩游記筆記評論.txt'url = 'http://www.mafengwo.cn/search/q.php?q=%E9%81%BF%E6%9A%91&t=notes&seid=D8276190-E622-4B11-A1B6-DF09CF22DD76&mxid=&mid=&mname=&kt=1' headers = {'Referer': 'http://www.mafengwo.cn/search/q.php?q=%E9%81%BF%E6%9A%91&t=notes&seid=D8276190-E622-4B11-A1B6-DF09CF22DD76&mxid=&mid=&mname=&kt=1','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' } allUrl = [] htmlUrl = requests.get(url,headers = headers).text txtUrl = etree.HTML(htmlUrl) file = txtUrl.xpath('//*[@id="_j_search_result_left"]/div/div/ul/li') for t in file:hrefUrl = t.xpath('./div/div[2]/h3/a/@href')[0]allUrl.append(hrefUrl)print (hrefUrl) print(len(allUrl))部分截屏,一共獲取46條鏈接
2.1.2、打開文章,下拉加載數據
使用selenium對js進行操作
for t in range(1000,310000,1000):#第一次下拉1000像素,第二次要大于1000,相當于從零算起,310000是它的總像素time.sleep(0.1)#延時,模擬人為加載js=f"document.documentElement.scrollTop={t}"#下拉加載driver.execute_script(js)2.1.3、加載數據完畢,獲取HTML,并關閉瀏覽器
由于頁面太多,使用瀏覽器的頁面后要關閉瀏覽器,減少電腦CPU的消耗,也是桌面整潔
source = driver.page_source#獲取源碼driver.close()#關閉瀏覽器2.1.4、提取作者筆記和評論
# 提取作者筆記soup = BeautifulSoup(source, "html.parser")note = soup.find(attrs={"class": "_j_content_box"}).get_text()note = note.replace("\n","")note = str(note) #提取筆記評論for usrAnswer in soup.find_all(attrs={"class": "mfw-cmt _j_reply_item"}):answer = usrAnswer.find(attrs={"class": "_j_reply_content"}).get_text()answer = str(answer)2.1.5、分開保存筆記和評論
分開保存的目的主要是為了方便研究,她們并沒有提要求,到這樣做比較保險。
# 保存作者筆記with open(filename,'a',encoding='utf-8') as f: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!f.write(note+"\n")f.close() #保存筆記評論with open(noteBook,'a',encoding='utf-8') as g: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!g.write(answer+"\n")g.close()2.1.6、大功告成,開始批量爬取
from selenium import webdriver import time import requests from lxml import etree from bs4 import BeautifulSoupfilename = 'G:馬蜂窩游記筆記文本.txt' noteBook = 'G:馬蜂窩游記筆記評論.txt' url = 'http://www.mafengwo.cn/search/q.php?q=%E9%81%BF%E6%9A%91&t=notes&seid=D8276190-E622-4B11-A1B6-DF09CF22DD76&mxid=&mid=&mname=&kt=1' headers = {'Referer': 'http://www.mafengwo.cn/search/q.php?q=%E9%81%BF%E6%9A%91&t=notes&seid=D8276190-E622-4B11-A1B6-DF09CF22DD76&mxid=&mid=&mname=&kt=1','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' } allUrl = [] htmlUrl = requests.get(url,headers = headers).text txtUrl = etree.HTML(htmlUrl) file = txtUrl.xpath('//*[@id="_j_search_result_left"]/div/div/ul/li') for t in file:hrefUrl = t.xpath('./div/div[2]/h3/a/@href')[0]allUrl.append(hrefUrl) for urlUser in allUrl:print("正在爬取:",urlUser)driver = webdriver.Chrome('D:\\Software\\chromedriver.exe')driver.get(urlUser)#打開馬蜂窩driver.implicitly_wait(6)#等待加載六秒time.sleep(6)for t in range(1000,310000,1000):#第一次下拉1000像素,第二次要大于1000,相當于從零算起,310000是它的總像素time.sleep(0.1)#延時,模擬人為加載js=f"document.documentElement.scrollTop={t}"#下拉加載driver.execute_script(js)source = driver.page_source#獲取源碼driver.close()#關閉瀏覽器# 提取作者筆記soup = BeautifulSoup(source, "html.parser")note = soup.find(attrs={"class": "_j_content_box"}).get_text()note = note.replace("\n","")note = str(note)# 保存作者筆記with open(filename,'a',encoding='utf-8') as f: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!f.write(note+"\n")f.close()#提取筆記評論for usrAnswer in soup.find_all(attrs={"class": "mfw-cmt _j_reply_item"}):answer = usrAnswer.find(attrs={"class": "_j_reply_content"}).get_text()answer = str(answer)#保存筆記評論with open(noteBook,'a',encoding='utf-8') as g: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!g.write(answer+"\n")g.close()print ("該頁評論已下載完畢!")終于解決了,看以來還行~
2.1.7、更具這樣的思路,完成了馬蜂窩攻略和問答的爬取
馬蜂窩攻略和問答與游記不同的地方在于,它倆的文章比較多,需要翻頁,這樣使用一個循環就構造好它的鏈接了,還是比較容易的,圓滿完成任務!
第三次項目交接
這次黃超同學重新更改了他的需求,需要重新爬取攜程網游記,從以前的一年之內改為時間不限,從800篇變到了9331篇
這樣的任務量無疑增加得大了,如果是按照先前的方式運行代碼一篇一篇的爬取,在運行順利的情況下,估計也要花兩三個小時吧。所以必須解決速度問題… …
方法一——使用多進程:
我先考慮使用多進程了完成這項工作,分別讓它往一個文本中寫入數據,多開幾個進程。考慮到請求數據庫失敗,代碼報錯的問題,我選擇使用try函數跳過錯誤的請求失敗的文章,執行后我發現被跳過的文章太多,如果要重新爬取這些文章或減少損失,這是一件不容易的事情。
方法二——分為10段,同時開10個程序:
我需要爬取的文章從1~9331,被我分為了10段執行,并分別把文本寫入10個txt文件,哪一個錯就重新執行哪一個,10個代碼同時執行。
第一個程序:第1~101篇,文件夾:filename = 'G:攜程游記之避暑01.txt' 第二個程序:第1001~201篇文件夾:filename = 'G:攜程游記之避暑02.txt' 第三個程序:第301~401篇文件夾:filename = 'G:攜程游記之避暑03.txt' ......... 第三個程序:第901~9332篇文件夾:filename = 'G:攜程游記之避暑10.txt'10個程序分開同時執行:
保存結果:
攜程問答爬取:
代碼片段其一:
import requests from bs4 import BeautifulSoup from lxml import etree import time start = time.time()#記錄開始時的時間 filename = "G:攜程問答之避暑3(1).txt"#保存的文本命名及路徑 headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' } allUrl = []#保存獲取的鏈接 for page in range(1,26,1):index = 'https://you.ctrip.com/searchsite/asks/?query=避暑&isAnswered=&isRecommended=&publishDate=&PageNo=%s'%pageprint ("正在獲取:",index)indexHtml = requests.get(url=index,headers=headers).textetreeIndex = etree.HTML(indexHtml)liUrls = etreeIndex.xpath('/html/body/div[2]/div[2]/div[2]/div/div[1]/ul/li')for li in liUrls:href = li.xpath('./dl/dt/a/@href')[0]hrefUrl = 'https://you.ctrip.com'+hrefallUrl.append(hrefUrl) urlNum = len(allUrl) print ("一共找到%s篇文章需要爬取"%urlNum) c = 0 for textUrl in allUrl:c = c + 1#記錄文章篇數if c in range(0,4676,10):time.sleep(50)#模擬暫停print ("正在爬取第%s篇文章,一共有4676篇"%c)textHtml = requests.get(url = textUrl,headers = headers).texttextsoup = BeautifulSoup(textHtml,"html.parser")#提取作者筆記標題textTitle = textsoup.find(attrs={"class":"ask_title"}).get_text()print (textTitle)textTitle = str(textTitle)with open(filename,'a',encoding='utf-8') as f: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!f.write(textTitle)f.close()#提取作者筆記titleQuestion = textsoup.find(attrs={"class":"ask_text"}).get_text()titleQuestion = str(titleQuestion)with open(filename,'a',encoding='utf-8') as f: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!f.write(titleQuestion)f.close()#提取評論answerBoxs = textsoup.find_all(attrs={"class":"answer_box cf"})for userAnswer in answerBoxs:answer = userAnswer.find(attrs={"class":"answer_text"}).get_text()answer = str(answer)with open(filename,'a',encoding='utf-8') as f: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!f.write(answer+"\n")f.close() end = time.time()#獲取當前時間 useTime = int(end-start) useTime = useTime/60 print ("本次爬取所有文章一共使用%s分鐘"%useTime)6個程序同時進行:
代碼運行結果:
文本保存結果:
第四次項目交接
我看見彭老師在群里給她的學生說,文本有空格和換行會影響數據分析,此刻她正在一個一個的刪除文本空格和換行。為了節約時間,我就告訴她她文本放入word使用替換功能去掉多余的空格和回車。
但是9千多篇文章還是太多了,她們的分析工具把電腦弄死機幾次后,重新決定只要三年的數據,但是攜程網上又沒有專門篩選三年數據的地方,擔心不能實現。
后來我通過提取每個游客的發表時間來提取文章,如果滿足2017,2018,2019年,就把它的鏈接追加在保存鏈接的數組中,達篩到的目的。
第一頁,1~101頁查找結果:
源碼如下:
第五次項目交接
三年的數據這樣存放不好使用分析,文件還是太大了,需要把2017,2018,2019年的數據單獨存放,于是又對代碼做出了一些更改,完成了最后一次需求!
import requests,time from lxml import etree from bs4 import BeautifulSoup start = time.time() filename = 'G:攜程游記之避暑201910.txt' headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36' } allUrl = [] c = 0 for page in range(901,932,1):#1,932,1indexUrl = 'https://you.ctrip.com/searchsite/travels/?query=避暑&isAnswered=&isRecommended=&publishDate=&PageNo=%s'%pageprint ("正在獲取:",indexUrl)indexHtml = requests.get(url=indexUrl,headers=headers).textetreeIndex = etree.HTML(indexHtml)ettimes = etreeIndex.xpath('/html/body/div[2]/div[2]/div[2]/div/div[1]/ul/li')for ettime in ettimes:try:pTime = ettime.xpath('./dl/dd[2]/text()')[1]pTime = pTime.split("發表于")[1]pTime = pTime.split("-")[0]href = ettime.xpath('./dl/dt/a/@href')[0]hrefUrl = 'https://you.ctrip.com' + hrefif int(pTime) == 2019:print ("符合條件的時間",pTime)print (hrefUrl)allUrl.append(hrefUrl)except:pass urlNum = len(allUrl) print ("一共找到%s篇文章需要爬取"%urlNum) for textUrl in allUrl:c = c + 1print ("正在爬取第%s篇文章,一共有%s篇"%(c,urlNum))try:html = requests.get(url = textUrl,headers=headers).textsoup = BeautifulSoup(html, "html.parser")try:title = soup.find(attrs={"class": "title1"}).get_text().replace("\n","").replace(" ","")except:title = soup.find(attrs={"class": "ctd_head_left"}).get_text().replace("\n","").replace(" ","")title = str(title)print ("正在爬取:",title)t = soup.find(attrs={"class": "ctd_content"})txt = t.get_text().replace("\n","").replace(" ","")txt = str(txt)with open(filename,'a',encoding='utf-8') as f: # 如果filename不存在會自動創建, 'w'表示寫數據,寫之前會清空文件中的原有數據!f.write(title)f.write(txt)f.close()except:pass end = time.time() useTime = int(end-start) useTime = useTime/60 print ("該次爬取這%s文章共使用%s分鐘"%(urlNum,useTime))運行結果截屏:
保存文本結果:
很感謝這次的實戰機會,通過很多次的需求,讓我學會了很多處理數據的方法,提高自己的編程實戰能力,幫上一點小忙。
總結
以上是生活随笔為你收集整理的携程旅游网与马蜂窝游客记录爬取的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浅析信息化项目的信息化绩效评价
- 下一篇: 计算机考研数据结构答案,计算机考研数据结