Python爬虫实战———微信实时爬取电影资讯
搞了个简单的小工具,实现了在微信上实时爬取网站电影资讯并自动回复的功能。
功能分析
好了,让我们看看是怎么个一回事。
网站电影的信息来源于豆瓣,都说豆瓣评分很公正客观,所以就选了这个作为目标源了。
目标功能:
用户输入任何带有电影字样的话(如:看电影),自动跳转到页面,提供 所有电影分类供用户选择。
用户选择任意一个类型后,分别反馈给用户按热度、时间、评论顺序排列的三份前十电影表单(电影名+评分)。
用户根据提供的电影,输入任意一个电影名后,将反馈给用户关于该电影的相关详细信息表单。
要求用户可以再次输入任意电影类型去搜其它电影或者此类型的任意其它电影。
网站页面分析:
这个页面的这些电影类型都是动态的信息(红色框),因此不能使用常规的request方法舒服的爬取了,这里将使用Selenium自动化测试工具来解决动态页面的爬取(之后会开一篇分享Selenium如何使用)。
这是点击电影进去后看到的详细信息,这些信息是静态的,在源码中有很好的体现
页面抓取分析:
抓取信息使用了Selenium中的Xpath定位动态数据,以及BeautifulSoup的方法定位静态数据,方法很多种不唯一,只供参考(后续马上开一篇归纳所有爬取信息的方法)。
微信对话:
与微信互动的方面,就使用简单的接口模块itchat实现,链接里面有详细的api介绍,https://itchat.readthedocs.io/zh/latest/api/
好了,到此基本的功能实现方法有了一个概况,下面看看源码。由于篇幅问题,这里贴上部分主要源码
源码分析
电影概况信息(电影名+评分):
1 | def browser_action_general_info(self, type_command): |
这里使用了Chrome浏览器作为模拟对象进行爬取,由于浏览器有点慢,操作间隙加了一些延迟,不然反应不过来。如果追求速度,也可以使用PhantomJS来代替Chrome。
使用Selenium的xpath定位对象,利用鼠标点击事件完成动态操作。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### 电影详细信息操作: ###
def browser_action_detail_info(self, counter, movie_name):
"""
chrome browser acts to crawl the detail info for users
:param counter:
:param movie_name:
:return:
"""
movie_click_num = 0
# click the type of movie
# 点击上次用户选择的电影类型
for num in range(0, len(movie_category)):
if command_cache[0] == movie_category[num]:
self.driver.find_element_by_xpath('//*[@id="content"]/div/div[1]/div/div[2]/div[1]'
'/form/div[1]/div[1]/label[{}]'.format(num+1)).click()
sleep(1)
# 点击选择用户选择的电影所在顺序排列
self.driver.find_element_by_xpath('//*[@id="content"]/div/div[1]/div/div[2]/div'
'[1]/form/div[3]/div[1]/label[{}]/input'.format(counter)).click()
sleep(1)
if counter == 1:
for x in range(0, len(movie_info_hot)):
if movie_name in movie_info_hot[x]:
movie_click_num = x + 1
elif counter == 2:
for x in range(0, len(movie_info_time)):
if movie_name in movie_info_time[x]:
movie_click_num = x + 1
else:
for x in range(0, len(movie_info_comment)):
if movie_name in movie_info_comment[x]:
movie_click_num = x + 1
# 点击电影名称进入详细页面,记录详细页面的url
movie_detail_url = self.driver.find_element_by_xpath('//*[@id="content"]/div/div[1]/div/div[4]/div/a[{}]'
.format(movie_click_num)).get_attribute('href')
return movie_detail_url
```
根据用户输入的电影名,查找其在详细列信息列表movie_info_all(三个顺序排列列表hot, time,
comment的顺序extend总和)的位置
进而定位电影名在哪个排列列表中里面,然后点击进去获得该电影的url
电影详细信息页面下载:
@staticmethod
def download_detail_info_html(url_target):
"""
download douban target html
:param url_target:
:return:
"""
try:
response = urllib.request.Request(url_target, headers=headers)
result = urllib.request.urlopen(response)
html = result.read().decode('utf-8')
# 返回下载的网页html,以供下一步进行数据提取
return html
except urllib.error.HTTPError as e:
if hasattr(e, 'code'):
print(e.code)
except urllib.error.URLError as e:
if hasattr(e, 'reason'):
print(e.reason)
1 | 根据上面返回的电影名url进行request下载,并返回下载的html。 |
电影详细信息解析(字段):
@staticmethod
def parse_detail_info(html_result):
"""
parse the html downloaded
:param html_result:
:return:
"""
# 清空详细信息列表
del movie_detail_info[:]
# 定义详细信息字段
movie_name = ''
actor_name_list = '主演: '
director_name = '导演: '
movie_type = '类型: '
movie_date = '上映日期: '
movie_runtime = '片长: '
soup = BeautifulSoup(html_result, 'lxml')
# 用BeautifulSoup方法从下载页面中提取字段信息
movie_name = movie_name + soup.find('span', property='v:itemreviewed').string.strip()\
+ soup.find('span', class_='year').string.strip()
director_name = director_name + soup.find('a', rel='v:directedBy').string.strip()
for x in soup.find_all('a', rel='v:starring'):
actor_name_list = actor_name_list + x.string.strip() + '/'
for x in soup.find_all('span', property='v:genre'):
movie_type = movie_type + x.string.strip() + '/'
for x in soup.find_all('span', property='v:initialReleaseDate'):
movie_date = movie_date + x.string.strip() + '/'
movie_runtime = movie_runtime + soup.find('span', property='v:runtime').string.strip()
# 将所有字段信息字符串放入详细信息列表中
movie_detail_info.append(movie_name)
movie_detail_info.append(director_name)
movie_detail_info.append(actor_name_list)
movie_detail_info.append(movie_type)
movie_detail_info.append(movie_date)
movie_detail_info.append(movie_runtime)
1 | 在函数体开头清空movie_detail_info,以准备用户下次的操作。 |
Wehchat微信数据交换接口:
@itchat.msg_register(itchat.content.TEXT, itchat.content.PICTURE)
def simple_reply(msg):
global movie_info_all
接受用户任意包含“电影”的字样,跳转到指定页面等待
if u’电影’ in msg[‘Text’]:
douban_object.browser_hotopen()
douban_object.cvt_cmd_to_ctgy_url(msg[‘Text’])
movie_category_option = ‘ ‘.join(douban_crawl.movie_category)
itchat.send_msg(‘—-请选择一种类型—-\n’ + movie_category_option, msg[‘FromUserName’])
接受用户的电影类型输入,并执行概况信息爬取,然后反馈给用户
elif msg[‘Text’] in douban_crawl.movie_category:
itchat.send_msg(‘正在查找’ + msg[‘Text’] + ‘电影…’, msg[‘FromUserName’])
del douban_crawl.command_cache[:]
douban_crawl.command_cache.append(msg[‘Text’])
# 进行概况信息爬取,并将所有排列列表扩展到一起
movie_info_all = douban_object.browser_action_general_info(msg['Text'])
itchat.send_msg('----按热度排序----\n' + '\n' + '\n'.join(douban_crawl.movie_info_hot),
msg['FromUserName'])
itchat.send_msg('----按时间排序----\n' + '\n' + '\n'.join(douban_crawl.movie_info_time),
msg['FromUserName'])
itchat.send_msg('----按评论排序----\n' + '\n' + '\n'.join(douban_crawl.movie_info_comment),
msg['FromUserName'])
接受用户的电影名的选择,并进行指定电影的详细字段爬取,然后返回给用户
else:
search_num = 0
for x in movie_info_all:
if msg[‘Text’] in x:
itchat.send_msg(‘正在查找’ + msg[‘Text’] + ‘…’, msg[‘FromUserName’])
loc = movie_info_all.index(x)
if 0 <= loc < 10:
search_num = 1
elif 10 <= loc < 20:
search_num = 2
else:
search_num = 3
break
url_result = douban_object.browser_action_detail_info(search_num, msg[‘Text’])
html_result = douban_object.download_detail_info_html(url_result)
douban_object.parse_detail_info(html_result)
itchat.send_msg(‘\n\n’.join(douban_crawl.movie_detail_info), msg[‘FromUserName’])`
movie_detail_info等的一些列表都是用了join方法字符串化了,并用n隔开。
总结归纳
模块使用
- 使用Selenium工具进行动态操作
- 使用request进行相应静态请求下载
- 使用Selenium的xpath进行数据定位和提取
- 使用BeautifulSoup进行数据提取
- 使用itchat完成微信对话数据交互
改进和完善
- 用户完成操作后一定时间内无反应浏览器自动关闭
- 多人同时发信息的并发问题
- 发生网络等中断错误时提示给用户 将电影的图片也一起返回给用户(现在下载的图片格式webp)