需求分析
爬虫是获取数据一种方式,能够按照一定规则自动抓取某个网站或者万维网信息的程序;现实环境中很大一部分网络访问都是由爬虫造成的;我们来看一个常见应用场景: 当我们使用百度或者其他搜索引擎搜索某个关键字的时候,搜索结果中会包含对应的内容,比如:搜索Python,搜索结果可能包括Python官网,Python相关文章等,可是这些信息分布在不同的网站,这些搜索引擎是如何知道这些信息与相对应的地址呢?也许是搜索引擎获取网站相关数据及对应的地址;python的官网应该不可能主动把相应数据给这些搜索引擎公司,那么这些数据是如何获取的呢?最可能的答案,搜索引擎公司按照一定的规则将这些网站的信息抓取下来,保存到本地,然后对数据进行清洗处理,这些数据是搜索网站的基础,而获取数据过程就是爬虫所做的事情。 根据爬取方式不同我们可以将爬虫分为两类:
1.增量型爬虫:不会限制抓取数据属性,比如Google, baidu搜索引擎都是增量型爬虫;他们无时无刻不在抓取数据,还会根据一定算法评价网站的好坏,定期抓取最新数据,以保证他们的搜索结果时效性,正确性;
2.批量型爬虫:限制抓取的属性,抓取特定网站的信息;此次我们选用的方法就是批量性爬虫。
我使用Python完成批量型爬虫的设计与实现,并对抓取数据进行清洗与分析,为什么选择Python? 当前比较火的语言之一,语法简单,容易上手,支持面向对象,丰富的第三方模块:
1.爬虫相关模块:requests、Bs4、 lxml等;
2.数据库相关模块:pyMySQL、pyMongo等;
3.数据分析相关模块:numpy,pandas,matplotlib等;
4.数据可视化模块:pyecharts
基于这些模块,快速的构建爬虫,抓取数据,并且对抓取的数据进行分析及可视化。
需求: 1、爬取豆瓣电影top250 2、获取电影名称、年份、评分、导演、编剧、主演、类型、国家、语言、时长等 3、将爬取的数据保存 4、对数据进行清洗,对错误数据在源码或数据中进行处理 5、进行数据分析及可视化
环境搭建 开发环境: python3.9, edge浏览器 开发平台: pycharm, jupyter notebook 用pip下载依赖库: requests, bs4, lxml, numpy, pandas, pyecharts等
数据爬取 1. 准备工作
url: https://movie.douban.com/top250 可以看到,在榜单上的信息较少,每个电影详细的信息需要进入链接到详细页面查看。 因此我们需要获取每个电影的详细页url. 以下为获取子页面url代码:
import requestsfrom lxml import etreeimport randomimport timeheaders = { "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/101.0.4951.54 Safari/537.36 Edg/101.0.1210.39 " } url = 'https://movie.douban.com/top250?start=0&filter=' resp = requests.get(url, headers=headers) resp.encoding = "utf-8" html = etree.HTML(resp.text) lis = html.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li" ) page_url = [] for li in lis: page = li.xpath("./div/div[2]/div[1]/a/@href" )[0 ] page_url.append(page) print (page_url)
输出结果
['https://movie.douban.com/subject/1292052/', 'https://movie.douban.com/subject/1291546/', 'https://movie.douban.com/subject/1292720/', 'https://movie.douban.com/subject/1292722/', 'https://movie.douban.com/subject/1295644/', 'https://movie.douban.com/subject/1292063/', 'https://movie.douban.com/subject/1291561/', 'https://movie.douban.com/subject/1295124/', 'https://movie.douban.com/subject/3541415/', 'https://movie.douban.com/subject/3011091/', 'https://movie.douban.com/subject/1889243/', 'https://movie.douban.com/subject/1292064/', 'https://movie.douban.com/subject/1292001/', 'https://movie.douban.com/subject/3793023/', 'https://movie.douban.com/subject/2131459/', 'https://movie.douban.com/subject/1291549/', 'https://movie.douban.com/subject/1307914/', 'https://movie.douban.com/subject/25662329/', 'https://movie.douban.com/subject/1292213/', 'https://movie.douban.com/subject/5912992/', 'https://movie.douban.com/subject/1296141/', 'https://movie.douban.com/subject/1291841/', 'https://movie.douban.com/subject/1849031/', 'https://movie.douban.com/subject/6786002/', 'https://movie.douban.com/subject/3319755/'] 进程已结束,退出代码0
可以看到我们只获得了一部分 需要修改start的值获取下一页的内容 以下,重构方法类,获取全部url
page_url = [] def one_page (url ): resp = requests.get(url, headers=headers) resp.encoding = "utf-8" html = etree.HTML(resp.text) lis = html.xpath("/html/body/div[3]/div[1]/div/div[1]/ol/li" ) for li in lis: page = li.xpath("./div/div[2]/div[1]/a/@href" )[0 ] page_url.append(page) resp.close() if __name__ == '__main__' : for i in range (10 ): one_page(f"https://movie.douban.com/top250?start={i * 25 } &filter=" ) time.sleep(random.randint(1 , 3 )) print (page_url)
输出:
['https://movie.douban.com/subject/1292052/', ......, 'https://movie.douban.com/subject/2297265/'] (链接过多此处省略) 进程已结束,退出代码0
可以看到所以子页面url都爬取出来了
2.子页面信息提取
子页面分析
为了方便后面采用的是jupyter实时输出方便修改代码。
from bs4 import BeautifulSoupfrom lxml import etree import pandas as pdimport numpy as npfilm_url='https://movie.douban.com/subject/1292052/' request =requests.get(film_url,headers=headers,timeout=10 ) request.encoding = 'utf-8' film_info=[] child_page=BeautifulSoup(request.text,'html.parser' ) rank = child_page.find(attrs={'class' : 'top250-no' }).text.split('.' )[1 ] film_name = child_page.find(attrs={'property' : 'v:itemreviewed' }).text.split(' ' )[0 ] director = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[1 ].split(':' )[1 ].split('/' ) scriptwriter = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[2 ].split(':' )[1 ].split('/' ) actor = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[3 ].split(':' )[1 ].split('/' ) filmtype = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[4 ].split(':' )[1 ].split('/' ) area = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[5 ].split(':' )[1 ].split('/' ) language = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[6 ].split(':' )[1 ].split('/' ) initialReleaseDate = min (child_page.find(attrs={'id' : 'info' }).text.split('\n' )[7 ].split(':' )[1 ].split('/' )).split('(' )[0 ] runtime = child_page.find(attrs={'property' : 'v:runtime' }).text rating_num = child_page.find(attrs={'property' : 'v:average' }).text stars5_rating_per = child_page.find(attrs={'class' : 'rating_per' }).text rating_people = child_page.find(attrs={'property' : 'v:votes' }).text film_info=[rank,film_name,director,scriptwriter,actor,filmtype,area,language,initialReleaseDate,runtime,rating_num,stars5_rating_per,rating_people] print (film_info)request.close() df = pd.DataFrame([film_info]) df
输出
清洗和预处理
有的电影子页面的索引项不同,比如7.千与千寻中出现了官方网站,导致数据出现错位。 同样的问题还存在于216.二十二中,此部为纪录片,没有演员等内容。 修改代码加上循环 两页面的与普通页面不同点:
head=['rank' ,'film_name' ,'director' ,'scriptwriter' ,'actor' ,'filmtype' ,'area' ,'language' ,'initialReleaseDate' ,'runtime' ,'rating_num' ,'stars5_rating_per' ,'rating_people' ] for i in range (len (page_url)): href=page_url[i] time.sleep(random.randint(2 ,7 )) r = requests.get(href,headers=headers) r.encoding = 'utf-8' child_page=BeautifulSoup(r.text,'html.parser' ) rank = child_page.find(attrs={'class' : 'top250-no' }).text.split('.' )[1 ] film_name = child_page.find(attrs={'property' : 'v:itemreviewed' }).text.split(' ' )[0 ] director = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[1 ].split(':' )[1 ].split('/' ) scriptwriter = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[2 ].split(':' )[1 ].split('/' ) actor = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[3 ].split(':' )[1 ].split('/' ) filmtype = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[4 ].split(':' )[1 ].split('/' ) if child_page.find(attrs={'id' : 'info' }).text.split('\n' )[5 ].split(':' )[0 ] == '官方网站' : area = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[6 ].split(':' )[1 ].split('/' ) language = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[7 ].split(':' )[1 ].split('/' ) initialReleaseDate = min (child_page.find(attrs={'id' : 'info' }).text.split('\n' )[8 ].split(':' )[1 ].split('/' )).split('(' )[0 ] elif rank == "216" : scriptwriter = '无' actor = '无' filmtype = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[2 ].split(':' )[1 ].split('/' ) area = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[3 ].split(':' )[1 ].split('/' ) language = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[4 ].split(':' )[1 ].split('/' ) initialReleaseDate = min (child_page.find(attrs={'id' : 'info' }).text.split('\n' )[5 ].split(':' )[1 ].split('/' )).split('(' )[0 ] else : area = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[5 ].split(':' )[1 ].split('/' ) language = child_page.find(attrs={'id' : 'info' }).text.split('\n' )[6 ].split(':' )[1 ].split('/' ) initialReleaseDate = min (child_page.find(attrs={'id' : 'info' }).text.split('\n' )[7 ].split(':' )[1 ].split('/' )).split('(' )[0 ] runtime = child_page.find(attrs={'property' : 'v:runtime' }).text rating_num = child_page.find(attrs={'property' : 'v:average' }).text stars5_rating_per = child_page.find(attrs={'class' : 'rating_per' }).text rating_people = child_page.find(attrs={'property' : 'v:votes' }).text film_info=[rank,film_name,director,scriptwriter,actor,filmtype,area,language,initialReleaseDate,runtime,rating_num,stars5_rating_per,rating_people] df = pd.DataFrame([film_info]) current_path = os.path.dirname('__file__' ) if film_info[0 ] == '1' : df.to_csv(current_path+'douban_top250_test.csv' , mode='a' , header=head, index=None , encoding="gbk" ) print (f"top{film_info[0 ]} 爬取完成" ) else : df.to_csv(current_path+'douban_top250_test.csv' , mode='a' , header=False , index=None , encoding= "gbk" ) print (f"top{film_info[0 ]} 爬取完成" )
加入线程池加快爬取速度
if __name__ == '__main__' : for i in range (10 ): one_page(f"https://movie.douban.com/top250?start={i * 25 } &filter=" ) time.sleep(random.randint(1 , 2 )) print ("url提取完毕!" ) with ThreadPoolExecutor(50 ) as t: for j in range (len (page_url)): childUrl = page_url[j] t.submit(child, childUrl) head = ['rank' , 'film_name' , 'director' , 'scriptwriter' , 'actor' , 'filmType' , 'area' , 'language' , 'initialReleaseDate' , 'runtime' , 'rating_num' , 'stars5_rating_per' , 'rating_people' ] data = pd.read_csv(r'douban_top250_test.csv' , header=None , names=head, encoding="gbk" ) data.to_csv('douban_top250_test.csv' , index=False , encoding="gbk" ) print ("爬取完毕!" )
效果: 可以看到并不是按顺序爬取的,说明线程池起到了作用,而且爬取速度较普通快几十倍
效果展示
排序后
特定数据的处理结果: