我们利用requests库和正则表达式来抓取猫眼电影TOP100的相关内容。requests比urllib使用更加方便,这里就选用正则表达式来作为解析工具。
(1)明确目标
我们要提取出猫眼电影TOP100的电影名称、时间、评分、图片等信息,提取的站点URL为http://maoyan.com/board/4,提取的结果会以文件形式保存下来。
(2)抓取网页
我们需要抓取的目标站点为http://maoyan.com/board/4,打开之后便可以查看到榜单信息。
将网页滚动到最下方,可以发现有分页的列表,直接点击第2页,观察页面的URL和内容发生了怎样的变化,如下图所示。
可以发现页面的URL变成http://maoyan.com/board/4?offset=10,比之前的URL多了一个参数,那就是offset=10,而目前显示的结果是排行11~20名的电影,初步推断这是一个偏移量的参数。再点击下一页,发现页面的URL变成了http://maoyan.com/board/4?offset=20,参数offset变成了20,而显示的结果是排行21~30的电影。
由此可以总结出规律,offset代表偏移量值,如果偏移量为n,则显示的电影序号就是n+1到n+10,每页显示10个。所以,如果想获取TOP100电影,只需要分开请求10次,而10次的offset参数分别设置为0、10、20、…90即可,这样获取不同的页面之后,再用正则表达式提取出相关信息,就可以得到TOP100的所有电影信息了。
(3)抓取首页
接下来用代码实现这个过程。首先抓取第一页的内容。我们实现了get_one_page()方法,并给它传入url参数。然后将抓取的页面结果返回,再通过main()方法调用。初步代码实现如下:
import requests
def get_one_page(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
return response.text
return None
def main():
url = 'http://maoyan.com/board/4'
html = get_one_page(url)
print(html)
main()
运行结果(部分内容)
(4)正则提取
可以看到,一部电影信息对应的源代码是一个dd节点,我们用正则表达式来提取这里面的一些电影信息。
而它的排名信息是在class为board-index的i节点内,这里利用非贪婪匹配来提取i节点内的信息,正则表达式写为:
.*?board-index.*?>(.*?)
后面有a节点,其内部有两个img节点。经过检查后发现,第二个img节点的data-src属性是图片的链接。这里提取第二个img节点的data-src属性,正则表达式可以改写如下:
.*?board-index.*?>(.*?).*?data-src="(.*?)"
电影名称在后面的p节点内,class为name,正则表达式可以改写如下:
.*?board-index.*?>(.*?).*?data-src="(.*?)".*?name.*?a.*?>(.*?)
提取主演名称
.*?board-index.*?>(.*?).*?data-src="(.*?)".*?name.*?a.*?>(.*?).*?star.*?>(.*?)
提取发布时间
.*?board-index.*?>(.*?).*?data-src="(.*?)".*?name.*?a.*?>(.*?).*?star.*?>(.*?).*?releasetime.*?>(.*?)
提取分数
注意分数由两部分组成,整数和小数部分,具体正则表达式如下
.*?board-index.*?>(.*?).*?data-src="(.*?)".*?name.*?a.*?>(.*?).*?star.*?>(.*?).*?releasetime.*?>(.*?).*?integer.*?>(.*?).*?fraction.*?>(.*?).*?
这样一个正则表达式可以匹配一个电影的结果,里面匹配了7个信息。接下来,通过调用findall()方法提取出所有的内容。
接下来,我们再定义解析页面的方法parse_one_page(),主要是通过正则表达式来从结果中提取出我们想要的内容,实现代码如下:
def parse_one_page(html):
pattern = re.compile(
'.*?board-index.*?>(.*?).*?data-src="(.*?)".*?name.*?a.*?>(.*?).*?star.*?>(.*?).*?releasetime.*?>(.*?).*?integer.*?>(.*?).*?fraction.*?>(.*?).*? ',re.S)
items = re.findall(pattern, html)
print(items)
最后把这个函数,放在main函数中执行。
def main():
url = 'http://maoyan.com/board/4'
html = get_one_page(url)
parse_one_page(html)
main()
运行结果(部分结果)
以上格式规范,如分数还是两部分,需要进一步规范,把这些数据输入到一个字典中,实现代码如下:
在parse_one_page(html)函数中插入
#整理信息
for item in items:
yield {
'index': item[0],
'image': item[1],
'title': item[2].strip(),
'actor': item[3].strip()[3:] if len(item[3]) > 30 else '',
'time': item[4].strip()[5:] if len(item[4]) > 5 else '',
'score': item[5].strip() + item[6].strip()
}
主函数修改为:
def main():
url = 'http://maoyan.com/board/4'
html = get_one_page(url)
for item in parse_one_page(html):
print(item)
把结果写入到文件中
这里直接写入到一个文本文件中。这里通过JSON库的dumps()方法实现字典的序列化,并指定ensure_ascii参数为False,这样可以保证输出结果是中文形式而不是Unicode编码。代码如下:
def write_to_json(content):
with open('result.csv', 'a',encoding='utf-8') as f:
f.write(json.dumps(content, ensure_ascii=False)+'\n')
main函数改为:
def main():
url = 'http://maoyan.com/board/4'
html = get_one_page(url)
for item in parse_one_page(html):
write_to_json(item)
分页抓取
因为我们需要抓取的是TOP100的电影,所以还需要遍历一下,给这个链接传入offset参数,实现其他90部电影的爬取,此时添加如下调用即可。
def main(filter):
ulr='https://movie.douban.com/top250?start='+str(filter)+'&filter='
html=get_one_page(ulr)
for item in parse_one_page(html):
write_to_json(item)
print(item)
执行语句如下:
if __name__=='__main__':
for i in range(10):
main(offset=i*10)
结果如图【部分结果】: