站在巨人的肩膀上!
人类社会已经进入大数据时代,大数据深刻改变着我们的工作和生活。随着互联网、移动互联网、社交网络等的迅猛发展,各种数量庞大、种类繁多、随时随地产生和更新的大数据,蕴含着前所未有的社会价值和商业价值!!!
一、前言 本文是一篇爬虫实战学习笔记,记录近些时日对爬虫的认识和学习心得,主要使用了 requests
、 re
、Beautifulsoup
和pandas
库,初学爬虫,代码写的有点烂,望包涵!
二、实例引入 假设由于工作或者项目要求,我们需要获取豆瓣电影 Top250 的影片数据,进行可视化分析。 数据包括 影片名
上映年份
评分
导演
主演
电影类别
上映地区
影片名言
等 原始的数据存放在豆瓣的网页上,像这样。
我们需要将数据采集下来,存放在一张 excel 表里
像这样!
然后对其进行可视化分析
像这样
这样
.......
试想一下,我们该怎么做?
天大寒,砚冰坚,手指不可屈伸,弗之怠,录毕,走送之,不敢稍逾约?
我想极少数的人会选择人工摘录,这是一个极不明智的选择。在信息时代,我们有计算机,我们有python,我们应该想些办法让计算机去做这些事情。
三、爬虫 爬虫,其实就是代替人力去完成信息抓取工作的一门技术,他能按照一定的规则,从互联网上抓取任何我们想要的信息。
四、爬取思路 如何写爬虫?我们写爬虫的思路是什么?
前文提到,爬虫是代替人去完成信息抓取工作的,那么接下我们需要思考的问题便是,人是如何完成信息抓取工作的。
首先,我们打开豆瓣电影 TOP250 排行榜,分析我们需要的数据存放在哪里,然后复制粘贴,把我们的数据存放在excel表格里,依次重复如此枯燥乏味的工作对吧。
是的,其实爬虫要做的工作也是如此,写爬虫的大致思路如下。
确定URL
——>发起请求获得服务器响应数据
——>解析数据
——> 数据存储
五、爬虫实战 1、单页爬取 先把单页爬取的代码放在这里,稍后我会做详细解释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 """ -*- coding: utf-8 -*- @Time : 2021/11/6 下午 4:59 @Author : SunGuoqi @Website : https://sunguoqi.com @Github: https://github.com/sun0225SUN """ import requestsimport refrom bs4 import BeautifulSoupimport pandas as pdurl = 'https://movie.douban.com/top250' headers = { 'User-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36' } response = requests.get(url, headers=headers) html = response.text soup = BeautifulSoup(html, 'lxml' ) all_li = soup.find('ol' , {'class' : 'grid_view' }).find_all('li' ) datas = [] for item in all_li: name = item.find('span' , {'class' : 'title' }).text score = item.find('span' , {'property' : 'v:average' }).text quote = item.find('span' , {'class' : "inq" }).text info = item.find_all('p' , {'class' : '' }) info_contents = info[0 ].text result = re.findall( '^.*?\u5bfc\u6f14:\s(.*?)\s.*?\u4e3b\u6f14:\s(.*?)\s.*?(\d{4})\s.*?([\u4e00-\u9fa5].*)\xa0.*?\u002f.*?([\u4e00-\u9fa5].*?)\s\s.*$' , info_contents, re.S) datas.append({ '片名' : name, '年份' : result[0 ][2 ], '评分' : score, '导演' : result[0 ][0 ], '主演' : result[0 ][1 ], '类型' : result[0 ][4 ], '国家/地区' : result[0 ][3 ], '经典台词' : quote }) print ("爬取完成!!!" )df = pd.DataFrame(datas) df.to_csv("豆瓣电影.csv" , index=False , header=True , encoding='utf_8_sig' ) print ("已写入豆瓣.csv文件" )
1.1、导入模块 首先我们需要导入四个模块,没有下面库的同学需要PIP
安装下。
1 2 3 4 import requestsimport refrom bs4 import BeautifulSoupimport pandas as pd
1.2、确定URL 我们请求的URL是明确的,就是https://movie.douban.com/top250?start=0&filter=
,其后面的参数是和多页爬取和过滤相关的,这个我们后面会用到。
1 url = 'https://movie.douban.com/top250'
1.3、发起请求 我们打开浏览器,输入网址,按下enter
键后便可获得精美的页面,但其实在这期间,计算机和浏览器为我们做了很多事情。
不妨我们试一下,打开我们的浏览器,输入网址https://movie.douban.com/top250
,然后按下我们电脑上的F12
键,打开开发者工具,选择Network
选项卡,刷新一下页面,你会看到很多数据包。这便是我们按下enter
键后获得的数据本身,浏览器根据相应的规则对这些数据包进行解析和渲染,便生成了我们见到的网页。
我们是通过浏览器去获取和解析数据的,那么爬虫如何像浏览器一样去请求数据呢?
站在巨人的肩膀上,Python大牛们已经解决了这个问题,并把它封装成了一个库,这个库便是requests
库,我们只需要调用库里面封装好的函数就可以模拟浏览器请求数据了。
似乎还需要讲一个东西,就是请求头
请求体
和响应头
响应体
的问题。
打开我们的开发者工具,点击一条数据,选择headers
选项卡,我们便可以看到此次请求的请求头,其中包括我们请求的URL
请求方法
UA标识
请求参数
等等
包裹是有身份的,就像我们收到的快递一样,数据包也是如此,我们需要知道这个数据是谁发送的,要干嘛,所以我们需要请求头
请求体
这样一个东西。
一些网站会设置反爬虫机制,如果服务器发现请求是python发送的,便不会正常响应,所以我们需要伪装一下身份。
解决方法就是利用请求头进行UA伪装
1 2 3 4 5 6 7 8 url = 'https://movie.douban.com/top250' headers = { 'User-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36' } response = requests.get(url, headers=headers)
如何查看自己浏览器的UA标识
呢?打开开发者工具,找到我们headers
选项卡,展开第三条数据即可看到我们电脑的UA
1.4、获得响应 如果程序正常运行,便会发送URL对应的资源文件,我们可以打印一下他的响应内容。
屏幕应该会打印一大堆HTML文本,我们的数据就存放在里面。
1.5、数据解析 现在,我们成功获取了HTML文件,我们需要的数据就存放在里面,但是如何过滤掉我们不需要的东西呢?
当米开朗琪罗被问及如何完成《大卫》这样匠心的雕刻作品时,他有一段著名的回答: ”很简单,你需要用锤子把石头上不像大卫的地方敲掉就行了。“
再次站在前人的肩膀上,BeautifulSoup
库闪亮出场。
在使用BeautifulSoup
库之前,我们应该很清楚的知道我们需要的数据存放在什么位置。
很显然,我们需要的数据存放在一个ol
有序列表里,每条数据便是一个列表项li
,每个li
标签又长什么样子呢?
因为豆瓣后台源代码有点乱,我们把它复制到vscode
里格式化一下再看。
我们需要的数据存放的位置就更加明显了。现在。。。我们可以喝一碗美味的汤了(BeautifulSoup)
先将我们获取的HTML文本封装成BeautifulSoup对象
,对象里包含了很多属性和方法,方便我们查找和获取我们需要的数据。
1 2 3 4 html = response.text soup = BeautifulSoup(html, 'lxml' )
这里我们首先获取所有的li
标签,然后遍历all_li
获得每个li
里的数据,在进行解析就可以了。
1 2 all_li = soup.find('ol' , {'class' : 'grid_view' }).find_all('li' )
我们创建一个空列表,将以后获得得每条数据,都存放在里面。
我们通过上面的分析发现,影片名称存放在下面这一小块。
1 2 3 4 5 6 7 8 <div class ="hd" > <a href ="https://movie.douban.com/subject/1292052/" class ="" > <span class ="title" > 肖申克的救赎</span > <span class ="title" > / The Shawshank Redemption</span > <span class ="other" > / 月黑高飞(港) / 刺激1995(台)</span > </a > <span class ="playable" > [可播放]</span > </div >
其对应的解析便是name = item.find('span', {'class': 'title'}).text
影片得分,存放在下面这一小块。
1 2 3 4 5 6 <div class ="star" > <span class ="rating5-t" > </span > <span class ="rating_num" property ="v:average" > 9.7</span > <span property ="v:best" content ="10.0" > </span > <span > 2478010人评价</span > </div >
其对应的解析便是name = item.find('span', {'class': 'title'}).text
影片语录存放在下面这一小块。
1 2 3 <p class ="quote" > <span class ="inq" > 希望让人自由。</span > </p >
其对应的解析便是quote = item.find('span', {'class': "inq"}).text
其他内容都在这里面,
1 2 3 4 <p class ="" > 导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...<br > 1994 / 美国 / 犯罪 剧情 </p >
有些同学可能会发现,如果我们依旧按照上面的方式去解析,我们只能获得p
标签里面的内容,没法把导演哇,主演哇,等等分离出来,emmm,怎么办呢?
魔法终究可以被魔法打败,我们有最强的字符串处理工具,就是正则表达式
。在使用之前,我们应该先引用先导入此模块。
首先我们获取的p
标签里的内容,它长下面这个样子。
1 2 导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /... 1994 / 美国 / 犯罪 剧情
其对应的解析便是 result = re.findall('^.*?\u5bfc\u6f14:\s(.*?)\s.*?\u4e3b\u6f14:\s(.*?)\s.*?(\d{4})\s.*?([\u4e00-\u9fa5].*)\xa0.*?\u002f.*?([\u4e00-\u9fa5].*?)\s\s.*$',info_contents, re.S)
这里关于正则表达式就不多说了,有兴趣的同学可以研究研究。
计算机科学领域有一个笑话,如果你有一个问题打算用正则表达式来解决,那么就是两个问题了。
于是,程序就变成下面这样了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for item in all_li: name = item.find('span' , {'class' : 'title' }).text score = item.find('span' , {'property' : 'v:average' }).text quote = item.find('span' , {'class' : "inq" }).text info = item.find_all('p' , {'class' : '' }) info_contents = info[0 ].text result = re.findall( '^.*?\u5bfc\u6f14:\s(.*?)\s.*?\u4e3b\u6f14:\s(.*?)\s.*?(\d{4})\s.*?([\u4e00-\u9fa5].*)\xa0.*?\u002f.*?([\u4e00-\u9fa5].*?)\s\s.*$' , info_contents, re.S)
接着我们把数据以字典的方式存放到列表里。
1 2 3 4 5 6 7 8 9 10 11 datas.append({ '片名' : name, '年份' : result[0 ][2 ], '评分' : score, '导演' : result[0 ][0 ], '主演' : result[0 ][1 ], '类型' : result[0 ][4 ], '国家/地区' : result[0 ][3 ], '经典台词' : quote })
OK,这样其实我们就把单张的豆瓣影片数据爬取完成了!
1.6、写入文件 写入文件用的是强大的pandas
库,这里需要注意下编码格式,否则打开的可能是乱码。
1 2 df = pd.DataFrame(datas) df.to_csv("豆瓣电影.csv" , index=False , header=True , encoding='utf_8_sig' )
2、我是如何“放弃”爬取多页数据的 接下来我们要做的问题就是多页爬取了,单页爬取对应的是一个URL
,多页爬取对应的当然就是多个URL
了
emmm,不太严格,严格来说应该是我们每次请求的URL
附加的参数变了,我们找到每次请求附加的参数变化规律就可以了。
第一页对应的URL:https://movie.douban.com/top250?start=0&filter=
第二页对应的URL:https://movie.douban.com/top250?start=25&filter=
…
第十页对应的URL:https://movie.douban.com/top250?start=225&filter=
很简单就发现了对吧,就是start
参数的值变了,于是我们可以这样构造URL
1 url = 'https://movie.douban.com/top250?start=' + str (k * 25 )
用for
循环遍历就好了。(当然还要注意data=[]要放在最外面,要不然获取每页数据时,data就被清空了)
1 2 3 4 for k in range(10): print("正在抓取第{}页数据...".format(k+1)) url = 'https://movie.douban.com/top250?start=' + str(k * 25) ......再把之前的代码加上去就可以了。
大功告成!!!
可是,真的这样么,我太天真了,现实给我来了当头一棒。
第二页数据就报错了,没有result[0][2]
条数据,也就是年份,emmm,其实不是年份,是因为我们写的正则表达式没有捕捉到主演信息,所以列表索引超了。仔细查找下问题,看下图!
好吧,我确实忽略这个问题了,因为这个top榜主要是简介,字数什么的有限制,并不能完成主演等等详细数据的爬取任务,而且我们也没有去写异常处理。
仔细分析后,网页内容不只这一条不符合规范,如果要加入异常处理的话,需要加入很多,况且数据也不全,所以我放弃爬取多页了???
3、我是如何完成爬取多页数据的 在参考了其他同类的爬虫文章后,我发现,top 250 页面只是电影简介,详情都在点开电影链接之后。
比如,我们打开《肖申克的救赎》这部电影,该电影的所有信息都会按规范的格式展现在了我们的面前。
我们再写一个爬虫,爬取每个电影的链接,然后打开电影详情链接,去解析详情文本就可以了。
具体代码如下,这个我就不做具体分析了,思路和上面差不多,最复杂的就是解析数据和数据清洗那里,需要一点点尝试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 """ -*- coding: utf-8 -*- @Time : 2021/11/7 下午 4:25 @Author : SunGuoqi @Website : https://sunguoqi.com @Github: https://github.com/sun0225SUN """ import reimport timeimport requestsfrom bs4 import BeautifulSoupimport pandas as pddatas = [] for k in range (10 ): print ("正在抓取第{}页数据..." .format (k + 1 )) url = 'https://movie.douban.com/top250?start=' + str (k * 25 ) headers = { 'User-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36' } r = requests.get(url, headers=headers) soup = BeautifulSoup(r.text, 'lxml' ) lists = soup.find_all('div' , {'class' : 'hd' }) for item in lists: href = item.a['href' ] time.sleep(0.5 ) response = requests.get(href, headers=headers) movie_soup = BeautifulSoup(response.text, 'lxml' ) name = movie_soup.find('span' , {'property' : 'v:itemreviewed' }).text.split(' ' )[0 ] year = movie_soup.find('span' , {'class' : 'year' }).text.replace('(' , '' ).replace(')' , '' ) score = movie_soup.find('strong' , {'property' : 'v:average' }).text votes = movie_soup.find('span' , {'property' : 'v:votes' }).text infos = movie_soup.find('div' , {'id' : 'info' }).text.split('\n' )[1 :11 ] director = infos[0 ].split(': ' )[1 ] scriptwriter = infos[1 ].split(': ' )[1 ] actor = infos[2 ].split(': ' )[1 ] filmtype = infos[3 ].split(': ' )[1 ] area = infos[4 ].split(': ' )[1 ] if '.' in area: area = infos[5 ].split(': ' )[1 ].split(' / ' )[0 ] language = infos[6 ].split(': ' )[1 ].split(' / ' )[0 ] else : area = infos[4 ].split(': ' )[1 ].split(' / ' )[0 ] language = infos[5 ].split(': ' )[1 ].split(' / ' )[0 ] if '大陆' in area or '香港' in area or '台湾' in area: area = '中国' if '戛纳' in area: area = '法国' times0 = movie_soup.find(attrs={'property' : 'v:runtime' }).text times = re.findall('\d+' , times0)[0 ] datas.append({ '片名' : name, '上映年份' : year, '评分' : score, '评价人数' : votes, '导演' : director, '编剧' : scriptwriter, '主演' : actor, '类型' : filmtype, '国家/地区' : area, '语言' : language, '时长(分钟)' : times }) print ("电影《{0}》已爬取完成..." .format (name)) df = pd.DataFrame(datas) df.to_csv("豆瓣电影top250.csv" , index=False , header=True , encoding='utf_8_sig' )
infos
那里直接提取这个div
里面所有的子孙节点的文本,返回的是一个列表,像下面这样,然后用索引去提取,再清洗下就可以存储到字典列表里了,还有要注意豆瓣反爬机制
,不要请求过快,time.sleep(0.5)
1 2 3 4 5 6 7 8 9 10 11 12 ['' , '导演: 弗兰克·德拉邦特' , '编剧: 弗兰克·德拉邦特 / 斯蒂芬·金' , '主演: 蒂姆·罗宾斯 / 摩根·弗里曼 / 鲍勃·冈顿 / 威廉姆·赛德勒 / 克兰西·布朗 / 吉尔·贝罗斯 / 马克·罗斯顿 / 詹姆斯·惠特摩 / 杰弗里·德曼 / 拉里·布兰登伯格 / 尼尔·吉恩托利 / 布赖恩·利比 / 大卫·普罗瓦尔 / 约瑟夫·劳格诺 / 祖德·塞克利拉 / 保罗·麦克兰尼 / 芮妮·布莱恩 / 阿方索·弗里曼 / V·J·福斯特 / 弗兰克·梅德拉诺 / 马克·迈尔斯 / 尼尔·萨默斯 / 耐德·巴拉米 / 布赖恩·戴拉特 / 唐·麦克马纳斯' , '类型: 剧情 / 犯罪' ,'制片国家/地区: 美国' ,'语言: 英语' ,'上映日期: 1994-09-10(多伦多电影节) / 1994-10-14(美国)' , '片长: 142分钟' ,'又名: 月黑高飞(港) / 刺激1995(台) / 地狱诺言 / 铁窗岁月 / 消香克的救赎' , 'IMDb: tt0111161' , '' ]
因为我们这次请求的链接,解析的文本确实比较多,所以我们需要稍等一会才可以拿到我们的数据了,不妨去喝杯咖啡~
六、数据可视化分析 Echarts 关于数据爬取我们就完成了,接下来我们要做的就是可视化分析。
可视化分析这块我还没有系统学习,以下内容是借鉴其他博主的。
参考链接:
https://blog.csdn.net/weixin_42512684/article/details/90708037
https://blog.csdn.net/weixin_42152811/article/details/115366846
1、导入pyecharts
模块 1 2 3 import pandas as pdfrom pyecharts import options as optsfrom pyecharts.charts import Bar
2、各地区上映电影数量前十 在线演示地址:https://box.sunguoqi.com/douban/01.html
源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 data = pd.read_csv('top250.csv' ) year_counts = data['上映年份' ].value_counts() year_counts.columns = ['上映年份' , '数量' ] year_counts = year_counts.sort_index() c = ( Bar() .add_xaxis(list (year_counts.index)) .add_yaxis('上映数量' , year_counts.values.tolist()) .set_global_opts( title_opts=opts.TitleOpts(title='各年份上映电影数量' ), yaxis_opts=opts.AxisOpts(name='上映数量' ), xaxis_opts=opts.AxisOpts(name='上映年份' ), datazoom_opts=[opts.DataZoomOpts(), opts.DataZoomOpts(type_='inside' )], ) .render('各年份上映电影数量.html' ) )
3、电影评价人数前二十 在线演示地址:https://box.sunguoqi.com/douban/02.html
源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 data = pd.read_csv('top250.csv' ) df = data.sort_values(by='评价人数' , ascending=True ) c = ( Bar() .add_xaxis(df['片名' ].values.tolist()[-20 :]) .add_yaxis('评价人数' , df['评价人数' ].values.tolist()[-20 :]) .reversal_axis() .set_global_opts( title_opts=opts.TitleOpts(title='电影评价人数' ), yaxis_opts=opts.AxisOpts(name='片名' ), xaxis_opts=opts.AxisOpts(name='人数' ), datazoom_opts=opts.DataZoomOpts(type_='inside' ), ) .set_series_opts(label_opts=opts.LabelOpts(position="right" )) .render('电影评价人数前二十.html' ) )
4、各年份上映电影数量 在线演示地址:https://box.sunguoqi.com/douban/03.html
源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 data = pd.read_csv('top250.csv' ) country_counts = data['国家/地区' ].value_counts() country_counts.columns = ['国家/地区' , '数量' ] country_counts = country_counts.sort_values(ascending=True ) c = ( Bar() .add_xaxis(list (country_counts.index)[-10 :]) .add_yaxis('地区上映数量' , country_counts.values.tolist()[-10 :]) .reversal_axis() .set_global_opts( title_opts=opts.TitleOpts(title='地区上映电影数量' ), yaxis_opts=opts.AxisOpts(name='国家/地区' ), xaxis_opts=opts.AxisOpts(name='上映数量' ), ) .set_series_opts(label_opts=opts.LabelOpts(position="right" )) .render('各地区上映电影数量前十.html' ) )
5、其他可视化分析实例 在线演示地址:https://box.sunguoqi.com/douban/04.html
在线演示地址:https://box.sunguoqi.com/douban/05.html
在线演示地址:https://box.sunguoqi.com/douban/06.html
七、后记 数据可视化还是很酷的,大家可以点进去网址查看,图表是可以动态交互的。 到此,本文就结束了!爬虫代码写的确实比较烂,并没有进行模块化编写以及异常处理,仅供交流! 欢迎关注小孙同学的个人公众号【不负人间理想】
,愿你我都可以不负人间理想,成为更好的自己!