2.3 使用Requests和正则表达式爬取猫眼电影Top 100排行榜
打开猫眼电影网站,进入到榜单页面,观察发现每页有10个电影,点击下一页时网站地址会添加一个参数offset
,参数值为10,20,30, ... , 100
: 如http://maoyan.com/board/4?offset=20
以此为入口地址,首先进行单页解析。按照如下步骤进行获取网站基本信息。
使用Chrome浏览器打开榜单页面
http://maoyan.com/board/4
鼠标右键检查,可以查看网络元素与浏览器渲染结果
选中
Network
标签,刷新网站,观察到浏览器与服务器之间有很多请求与响应,包括主要页面与css
、js
文件等等从服务器的主要Response中获取
Request headers
信息,用于爬虫中构造headers

从
Network
页签下的Response
页获取服务器的原始响应信息,一定要从此处获取而不是Element
处获取,因为Element
中的源码可能经过 JavaScript 的操作而和原始请求的不同。在Response
中页签中找到需要获取的信息,如:电影名,海报链接,演员,上映时间地点,排名,打分。
观察到每个电影均以<dd>...</dd>
标签来包含。
<dd>
<i class="board-index board-index-21">21</i>
<a href="/films/1249" title="黑客帝国" class="image-link" data-act="boarditem-click" data-val="{movieId:1249}">
<img src="//ms0.meituan.net/mywww/image/loading_2.e3d934bf.png" alt="" class="poster-default" />
<img data-src="http://p1.meituan.net/movie/d981a12f59d3cc92ff666094404ad8f0211220.jpg@160w_220h_1e_1c" alt="黑客帝国" class="board-img" />
</a>
<div class="board-item-main">
<div class="board-item-content">
<div class="movie-item-info">
<p class="name"><a href="/films/1249" title="黑客帝国" data-act="boarditem-click" data-val="{movieId:1249}">黑客帝国</a></p>
<p class="star">
主演:基努·里维斯,凯瑞-安·莫斯,劳伦斯·菲什伯恩
</p>
<p class="releasetime">上映时间:2000-01-14</p> </div>
<div class="movie-item-number score-num">
<p class="score"><i class="integer">9.</i><i class="fraction">0</i></p>
</div>
</div>
</div>
</dd>
利用正则表达式
(.*?)
过滤出各个元素.
<dd>.*?board-index.*?">(.*?)</i>.*?title="(.*?)".*?data-src="(.*?)".*?movie-item-info.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>
解析一下这个正则表达式:
<dd>.*?board-index
表示找到以<dd>
开头的字符串后,继续往后匹配,.*?
表示匹配任意个任意字符,直到第一次(问号表示的非贪婪模式)遇到board-index
字符串后结束这段匹配。.*?">(.*?)</i>
表示的是,在前面的匹配找到后,继续进行匹配任意个任意字符,直到第一次遇到">
字符串,通过(.*?)</i>
匹配提取括号中的任意个任意字符串,直到第一次遇到</i>
字符串。
上面的这两段能够匹配出
<dd> <i class="board-index board-index-21">21</i>
,提取出排名21
字符串。
继续匹配,后面的正则表达式
.*?title="(.*?)"
表示匹配任意个任意字符,直到第一次遇到title="
字符串,将"
符号后面的字符串使用(.*?)"
进行匹配,提取第二个"
之前的字符串。
即能匹配提取
title="黑客帝国"
中的黑客帝国
。
继续匹配,
.*?data-src="(.*?)"
,表示匹配任意个任意字符,直到第一次遇到data-src="
字符串,使用(.*?)
提取其后面的字符串。后续匹配类似,即使用
.*?
匹配任意个任意字符,并使用非贪婪模式,需要提取的字符串使用()
括起来。
基于上面的分析,可以编写网页获取函数与解析函数。
import requests
import re
#导入urllib库用于做异常处理
import urllib
from urllib import error
def get_one_page(url):
headers = {
#在浏览器中观察获取Cookie,host,user-agent参数
'Cookie': 'uuid_n_v=v1; uuid=6266F950913511E8BAE959978C7B0D0473005491BEDE4F03918D808909FBCD63; _csrf=ac998ee17cc0a0a19ead8ae7bad340ccd342180e2588ba8b326ee6d6b4deac25; _lxsdk_cuid=164d92c0d5a25-0e046d8f7973c-16386952-100200-164d92c0d5bc8; _lxsdk=6266F950913511E8BAE959978C7B0D0473005491BEDE4F03918D808909FBCD63; __mta=152329862.1532651901646.1532653173231.1532653961468.14; _lxsdk_s=164d92c0d5c-908-59-aa6%7C%7C56',
'Host': 'maoyan.com',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
}
#使用try处理异常
try:
response = requests.get(url,headers=headers)
return response.text
except error.HTTPError as err:
print(err.reason, err.code, err.headers, sep='\n')
return None
except urllib.error.URLError as err:
print(err.reason)
return None
def parse_one_page(html):
#定义正则表达式
reg = r'<dd>.*?board-index.*?">(.*?)</i>.*?title="(.*?)".*?data-src="(.*?)".*?movie-item-info.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>'
#使用re模块匹配查询出所有匹配正则的字符串,返回字符串列表
results = re.findall(reg,html,re.S)
# print(results)
for item in results:
# 使用yield进行迭代返回
yield {
'排名': item[0],
'电影': item[1],
'海报链接': item[2],
# 添加条件判断语句,增加程序的健壮性,只有当item[3]长度大于3时才从第三个开始获取
'演员': item[3].strip()[3:] if len(item[3])>3 else '',
# 添加条件判断语句,增加程序的健壮性,只有当item[4]长度大于5时才能获取上映时间
'上映时间': item[4].strip()[5:15] if len(item[4]) > 5 else '',
# 通过正则表达式获取括号()里的字符串,且添加条件判断语句只有匹配到了才获取其中的字符串,
'上映地点': re.findall(r'\((.*?)\)',item[4],re.S)[0] if len(re.findall(r'\((.*?)\)',item[4],re.S))>0 else '',
# 将原有的字符串变成数字,将评分前半段去掉点号,小数位除以10
'猫眼评分': int(item[5].strip('.'))+int(item[6])/10
}
#保存结果到文本文件
def write_to_json(content):
#content 参数就是一部电影的提取结果,是一个字典。
with open('result.txt', 'a', encoding='utf-8') as f:
f.write(json.dumps(content, ensure_ascii=False) + '\n')
#抓取单个网页,并解析
def single_page(offset):
url = 'http://maoyan.com/board/4?offset='+str(offset*10)
html = get_one_page(url)
for item in parse_one_page(html):
print(item)
write_to_json(item)
if __name__ == '__main__':
#抓取所有10页
for page in range(10):
single_page(page)
Last updated