Python中的selenium模块是可以启动浏览器与人交互。我们知道requests模块在网页下载时配合上BeautifluSoup会简单很多,但是随着很多网站越来越注重反爬,会拒绝提供页面,而selenium模块通过启动浏览器像和普通浏览器一样的流量模式使你更容易被接受下载信息,同时长期有效性也会大很多。
这次我们的实例是通过爬取网站IMDb获取电影分类排名数据。需要说明的是这个网站在我们国内登录会有些困难,但是会登的上去,只是没那么顺畅。
同样首先我们先确定好我们的任务:
- selenium模块应用的前期准备。
- IMDb网页分析。
- 通过selenium获取想要的数据信息。
- 将信息格式化。
- 格式化后的信息存储到json,sqlite及excel中。
为此我们需要完成以下操作:
- selenium模块安装及安装浏览器的webdriver。
- 确定电影排名的网址以及网页源代码(html)内信息所在的元素。
- 通过find_elements_*方法寻找元素并通过.text方法获取元素内文本。
- 通过对爬取的文本进行格式化为字典模式并添加到列表中方便处理。
- 通过json将每个类别的电影数据分别写入一个json文件,然后将所有的电影汇总写入一个json文件,最后通过sqlite3,openpyxl模块将所有的json文件写入sqlite及excel文件中。
由于内容较多我们分两篇来练习,今天就先完成1-4部分。话不多说就开始练习吧。
首先,准备工作,安装selenium模块这个就不赘述了,大家自行安装。
其次准备工作的另外一项是安装相应的浏览器的webdriver,我自己用的是chrome浏览器,所以需要去ChromePriver下载Windows系统的版本,然后将chromedriver.exe添加到PATH路径上。如果你用的其它浏览器如Firefox,请自行去下载相应的webdriver并添加到环境变量中。
接下来到了分析电影网站的步骤,我们先打开其主页,点击menu中IMDb Top 250 Movies的选项进入top250排名页面会发现此页上的电影排名并没有详细的信息,而且是个总的排名,但是我们注意到页面右边有Top Rated Movies by Genre,这表示每个分类的电影排名,先点击Action动作片分类,进去页面这里提供给我们的信息有:这个排名是在每部至少出租数25000,按照降序排列,每页有50部,默认的是按照IMDb评分排列的,并且有每部电影的详细信息如时长,评分,媒体评分,电影简介,导演,演员,投票数,票房等,这正是我们所需要的,Perfect!
再来分析分析网址的构成,网址如下:
https://www.imdb.com/search/title/?genres=action&sort=user_rating,desc&title_type=feature&title_type=25000,&pf_rd_m=A2FGELUUNOQJNL&pf_rd_p=5aab685f-35eb-40f3-95f7-c53f09d542c3&pf_rd_r=KXNCSE4DS589EV2SE77Q&pf_rd_s=right-6&pf_rd_t=15506&pf_rd_i=top&ref_=chttp_gnr_1
我们看下问号后面的部分,看着挺长,但是我们需要的是
genres,sort,title_type,title_type,同时还有个start表示从第几个开始,上面提到了一页有50个电影,所以start=51则表示第二页,以此类推。
所以我们需要请求的网址为:
https://www.imdb.com/search/title/?genres=action&sort=user_rating,desc&title_type=feature&title_type=25000,&start=1。
网址确定了,下面分析网页的元素,F12打开开发者工具,找到我们所需信息的元素,会发现每部电影的信息都包含在css类名为lister-item-content的div元素内。OK,至此我们完成了页面的分析,下面开始下载数据。
首先我们建立imdbmovies_ranking_bygenre.py文件,首先引入模块:
from selenium import webdriver
webdriver提供了很多寻找元素的方法如下:
#使用class类name的元素:
browser.find_element_by_class_name(name)
browser.find_elements_by_class_name(name)
#匹配css selector的元素
browser.find_element_by_css_selector(selector)
browser.find_elements_by_css_selector(selector)
#匹配id属性值的元素
browser.find_elements_by_id(id)
browser.find_elements_by_id(id)
#完全匹配提供的text的<a>元素
browser.find_element_by_link_text(text)
browser.find_elements_by_link_text(text)
#包含提供的text的<a>元素
browser.find_element_by_partial_link_text(text)
browser.find_elements_by_partial_link_text(text)
#匹配name属性值的元素
browser.find_element_by_name(name)
browser.find_elements_by_name(name)
#匹配标签name的元素
browser.find_element_by_tag_name(name)
browser.find_elements_by_tag_name(name)
需要说明的是browser.find_element和browser.find_elements的区别为,browser.find_element返回第一个匹配的元素,而browser.find_elements则包含页面中匹配的所有元素。同时除了by_tag_name不区分大小写,其它的所有参数都区分大小写。以上所有方法返回的是webelement对象,有了此对象通过text方法获取元素内的文本,所以我们的代码如下:
def movie_lst_data(genres):
"""Get the data from url"""
movie_lst = []
for i in ['1','51']:
options = webdriver.ChromeOptions()
options.add_argument('lang=en_US')
browser = webdriver.Chrome(chrome_options = options)
start = i
url = 'https://www.imdb.com/search/title?title_type=feature&num_votes=25000,&genres={}&sort=user_rating,desc&start={}&ref_=adv_nxt'\
.format(genres,start)
browser.get(url)
elems = browser.find_elements_by_class_name('lister-item-content')
for elem in elems:
movie_dict = {
}
movie_str = elem.text
#print(movie_str)
对代码进行一下说明,
options = webdriver.ChromeOptions()
options.add_argument('lang=en_US')
browser = webdriver.Chrome(chrome_options = options)
这三行是打开浏览器,我们要使浏览器用默认语言为英语的状态下打开浏览器,为什么呢,和IMDb网站的语言支持政策有关,大家可以自行去查看。
打开浏览器之后,我们需要通过browser.get(url) 输入网址并打开网页,这里把其放入for循环中,因为我想每个分类爬取100部(不想太多是鉴于这个网站打开并不顺畅),然后函数内传递类别的参数,这样就构成了我们网址,打开网页后通过browser.find_elements_by_class_name获取所有元素,再通过一个for循环遍历每个元素,然后通过text方法获取每个元素的内容(注意text方法只能获取单个元素的文本,所有我们需要遍历所有元素)。通过打印出来可以得知每个元素内的文本内容如下:
2. The Lion King (1994)\n
G | 88 min | Animation, Adventure, Drama\n
8.5 Rate this\n
88 Metascore\n
Lion prince Simba and his father are targeted by his bitter uncle, who wants to ascend the throne himself.\n
Directors: Roger Allers, Rob Minkoff | Stars: Matthew Broderick, Jeremy Irons, James Earl Jones, Whoopi Goldberg\n
Votes: 1,019,044 | Gross: $422.78M\n
这是一个字符串,所以我们要把这个字符串格式化添加到字典中去,所以后面代码如下:
...
*movie_str = elem.text*
# Convert str to list
movieLst = movie_str.split("\n")
# Generate dict
movie_dict["Rank"] = movieLst[0].split('.')[0]
if len(movieLst[0].split('.')[0]) == 3:
movieName = movieLst[0][4:-7].strip()
movie_dict['Movie'] = movieName
else:
movie_dict['Movie'] = movieLst[0][3:-7].strip()
movie_dict['Year'] = movieLst[0][-5:-1]
if len(movieLst[1].split('|'))==2:
movie_dict['Duration'] = movieLst[1].split('|')[0].strip()
movie_dict['Movie Genres'] = movieLst[1].split('|')[1].strip()
elif len(movieLst[1].split('|'))==3:
movie_dict['Duration'] = movieLst[1].split('|')[1].strip()
movie_dict['Movie Genres'] = movieLst[1].split('|')[2].strip()
movie_dict['IMDb Score'] = movieLst[2].split(' ')[0]
if len (movieLst) == 7:
movie_dict['Metascore'] = movieLst[3].split(' ')[0]
movie_dict['Plot'] = movieLst[4]
movie_dict['Director'] = movieLst[5].split('|')[0].split(":")[1].strip()
movie_dict['Stars'] = movieLst[5].split('|')[1].split(":")[1].strip()
if "|"in movieLst[6]:
movie_dict['Votes'] = movieLst[6].split('|')[0].split(":")[1].strip()
movie_dict['Gross'] = movieLst[6].split('|')[1].split(":")[1].strip()
else:
movie_dict['Votes'] = movieLst[6].split('|')[0].split(":")[1].strip()
movie_dict['Gross'] = "NO DATA"
elif len (movieLst) == 6:
movie_dict['Metascore'] = "NO DATA"
movie_dict['Plot'] = movieLst[3]
movie_dict['Director'] = movieLst[4].split('|')[0].split(":")[1].strip()
movie_dict['Stars'] = movieLst[4].split('|')[1].split(":")[1].strip()
if "|"in movieLst[5]:
movie_dict['Votes'] = movieLst[5].split('|')[0].split(":")[1].strip()
movie_dict['Gross'] = movieLst[5].split('|')[1].split(":")[1].strip()
else:
movie_dict['Votes'] = movieLst[5].split('|')[0].split(":")[1].strip()
movie_dict['Gross'] = "NO DATA"
movie_lst.append(movie_dict)
上述代码中我们首先把每个字符转为列表,然后把每项信息添加到字典中,注意的是列表中有些元素会有些变化,例如媒体评分若是没有则列表的长度就会变为6,而包含时长的元素通过“|”分出的列表长度会有两种情况,还有有些电影没有票房数据等等这些都会影响我们对列表的引用,所以要把数据进行周全的分析以避免错误。
运行无误后每部电影的信息如下所示的字典格式添加到了列表中:
[
{
"Rank": "1",
"Movie": "K.G.F: Chapter 2",
"Year": "2022",
"Duration": "168 min",
"Movie Genres": "Action, Crime, Drama",
"IMDb Score": "9.0",
"Metascore": "NO DATA",
"Plot": "In the blood-soaked Kolar Gold Fields, Rocky's name strikes fear into his foes. While his allies look up to him, the government sees him as a threat to law and order. Rocky must battle threats from all sides for unchallenged supremacy.",
"Director": "Prashanth Neel",
"Stars": "Yash, Sanjay Dutt, Srinidhi Shetty, Raveena Tandon",
"Votes": "78,538",
"Gross": "NO DATA"
},
......
注意每个页面数据下载完成后要关闭浏览器,不然要打开很多个。最后我们的函数返回我们下载完成的列表:
......
browser.close()
# print(elem.text)
return movie_lst
然后我们再建立一个函数,将每部电影的数据写入一个json文件中:
def movie_json_data(movie_lst,genres):
"""Generate the json data"""
with open('json_data/{}_movies(100).json'.format(genres), 'w',encoding='utf-8') as json_file:
json.dump(movie_lst, json_file, indent=4, ensure_ascii=False)
最后运行函数:
if __name__ == "__main__":
try:
"""genres = [ 'Action','Adventure','Animation' 'Biography','Comedy','Crime', 'Drama','Family','Fantasy', 'Film_Noir','History','Horror', 'Music','Musical','Mystery', 'Romance','Sci_Fi','Sport', 'Thriller','War','Western', ]"""
genres = "Action"
movie_lst = movie_lst_data(genres)
movie_json_data(movie_lst,genres)
except Exception as e:
print("ERROR: "+ str(e))
这里需要说明的是,本来类别也可以通过for循环类别列表自动完成,但是考虑到这个网站的登录变数较大,所以还是手动运行每个类别比较好。链接不上的情况下程序会退出,这时要重新运行,有时候需要运行多几次才能成功,如Action类别的电影成功运行如下图所示:
每个网页运行完成后会自动关闭浏览器,然后再次打开第二页,完成后再次关闭,最后数据都写入json文件,当所有的类别都完成后我们会看到我们的文件夹内生成了所有的类别电影的排名100的文件,
而生成的json文件内容如下:
至此我们前4项的工作和生成json文件部分部分也就完成了,这也我们这个实例的第一部分。
后面第二部分我们会将每个类别的电影数据汇聚成一个按照IMDb分数排行总数据写入json,同时将所有的json数据写入到sqliet和excel中去。
(本来文章有很多图片展示的,但图片多了总是待审核,所以无奈删除了一些)
未完待续…
文章评论