赞
踩
前言:这是我学习Python爬虫以来,第一次使用python进行大规模的进行数据挖掘。邀请我加入她们科研项目的是工商学院的彭老师,做一个关于避暑旅游的课题。当他们需要获取携程旅游笔记时,由于文章的内容太多,思路也没有考虑好,无法使用数据采集器把笔记放入Excel。于是找到了我们信息学院寻求方法帮助,我的辅导员老师就向她推荐了我。终于有机会进行大型的项目实战了,非常幸运能参与这次的项目。但也由于平时比较繁忙,真正开发的时间并不是很多,通常都是课余时间做的。
她安排她的学生黄超来与我进行交接,告诉我,他们的需求。以前数据的采集就是他负责的,所以需求还是非常清楚的。他打电话与我沟通,说需要把旅游的笔记放进Excel做数据分析,我不是很理解,对于这些不规则的文本,最好的方式应该是放进txt文本才方便呀,经过一些沟通后,确定第二天见面谈。我需要在周末上双学位的课程,10月13号下午下课后就在我们上课的教室讨论,指出我需要爬取的内容。
当天也相当于交流学习,我从未做过这方面的实战。听他的要求后,最理想的方式是把它文字保存在txt文本,并不是Excel,初步达成共识。
第一次爬取:携程网,游客游记,关键词“避暑旅游”,不限时间,初次搜索出4398条
由于数量太大,更改关键词为“避暑”,时间为1年内,游记,共找到800篇,作为这次爬取的目标。由于是初次做这样的工作,变弄便沟通,花了3多小时的时间才完成初步的工作,实现思路如下:
通过分析HTML发现,我所需要获取的文字都在class="ctd_content"
这个标签类,直接通过BeautifulSoup的find模块找到所有的文本就可以了;
运行结果如下:
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的过程中,发现很多文本会提示类型有有误,转成字符串后就没有报错了。
现在算是成功最主要的一步了,接下来就是爬取所有的文章了。
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、减少被反爬的概率,经常访问更容易被识别出爬虫
3、优化逻辑
import requests from lxml import etree from bs4 import BeautifulSoup 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 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).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 allUrl.append(hrefUrl)
import requests from lxml import etree from bs4 import BeautifulSoup 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' } 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).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 allUrl.append(hrefUrl) for useUrl in allUrl: k = k + 1 print ("正在获取第%s篇"%k) print (useUrl) html = requests.get(url = useUrl,headers=headers).text soup = 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篇,速度已经慢得难受了,还好是上课时间,听了一节课,不影响我时间的使用。
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条链接
使用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)
由于页面太多,使用浏览器的页面后要关闭浏览器,减少电脑CPU的消耗,也是桌面整洁
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)
#提取笔记评论
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(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()
from selenium import webdriver import time 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) 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 ("该页评论已下载完毕!")
终于解决了,看以来还行~
马蜂窝攻略和问答与游记不同的地方在于,它俩的文章比较多,需要翻页,这样使用一个循环就构造好它的链接了,还是比较容易的,圆满完成任务!
这次黄超同学重新更改了他的需求,需要重新爬取携程网游记,从以前的一年之内改为时间不限,从800篇变到了9331篇
这样的任务量无疑增加得大了,如果是按照先前的方式运行代码一篇一篇的爬取,在运行顺利的情况下,估计也要花两三个小时吧。所以必须解决速度问题… …
我先考虑使用多进程了完成这项工作,分别让它往一个文本中写入数据,多开几个进程。考虑到请求数据库失败,代码报错的问题,我选择使用try函数跳过错误的请求失败的文章,执行后我发现被跳过的文章太多,如果要重新爬取这些文章或减少损失,这是一件不容易的事情。
我需要爬取的文章从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'%page print ("正在获取:",index) indexHtml = requests.get(url=index,headers=headers).text etreeIndex = 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'+href allUrl.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).text textsoup = 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年,就把它的链接追加在保存链接的数组中,达筛到的目的。
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' + href
if int(pTime) in [2017,2018,2019]:
print ("符合条件的时间",pTime)
print (hrefUrl)
allUrl.append(hrefUrl)
except:
pass
第一页,1~101页查找结果:
源码如下:
import requests,time from lxml import etree from bs4 import BeautifulSoup start = time.time() filename = 'G:携程游记之避暑201901.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,101,1):#1,932,1 indexUrl = 'https://you.ctrip.com/searchsite/travels/?query=避暑&isAnswered=&isRecommended=&publishDate=&PageNo=%s'%page print ("正在获取:",indexUrl) indexHtml = requests.get(url=indexUrl,headers=headers).text etreeIndex = 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' + href if int(pTime) in [2017,2018,2019]: print ("符合条件的时间",pTime) print (hrefUrl) allUrl.append(hrefUrl) except: pass urlNum = len(allUrl) print ("一共找到%s篇文章需要爬取"%urlNum) c = 0 for textUrl in allUrl: c = c + 1 print ("正在爬取第%s篇文章,一共有%s篇"%(c,urlNum)) try: html = requests.get(url = textUrl,headers=headers).text soup = 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))
三年的数据这样存放不好使用分析,文件还是太大了,需要把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,1 indexUrl = 'https://you.ctrip.com/searchsite/travels/?query=避暑&isAnswered=&isRecommended=&publishDate=&PageNo=%s'%page print ("正在获取:",indexUrl) indexHtml = requests.get(url=indexUrl,headers=headers).text etreeIndex = 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' + href if int(pTime) == 2019: print ("符合条件的时间",pTime) print (hrefUrl) allUrl.append(hrefUrl) except: pass urlNum = len(allUrl) print ("一共找到%s篇文章需要爬取"%urlNum) for textUrl in allUrl: c = c + 1 print ("正在爬取第%s篇文章,一共有%s篇"%(c,urlNum)) try: html = requests.get(url = textUrl,headers=headers).text soup = 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))
运行结果截屏:
保存文本结果:
很感谢这次的实战机会,通过很多次的需求,让我学会了很多处理数据的方法,提高自己的编程实战能力,帮上一点小忙。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。