requests+正则表达式+multiprocessing多线程抓取猫眼电影TOP100
本文介绍利用Requests库、multiprocessing库和正则表达式爬取猫眼电影TOP100电影的相关信息,提取出电影名称、上映时间、评分、封面图片等信息,将爬取的内容写入到文件中。站点URL为 http://maoyan.com/board/4
准备
本文使用了Requests库,使用pip安装: pip install requests
分析
打开http://maoyan.com/board/4,可以看到榜单信息。如下图所示
排名第一的电影是霸王别姬,可以提取的信息有电影名称、主演、上映时间、评分、封面图等。
点击页面下方的分页列表翻页到第二页,会发现URL会变成https://maoyan.com/board/4?offset=10
,比首页多了个offset=10
参数,而目前显示的是排名11-20的电影,初步判断这是偏移量参数。再点击下一页,URL变成了https://maoyan.com/board/4?offset=20
,offset
变成了20,显示的是排名21-30的电影。
由此可见,offset代表偏移量,偏移量为n,则显示的是排名n+1
~n+10
的电影,每页显示10个电影。所以,想要获取TOP100电影信息,只要分开获取10次,只需把10次请求的URL中offset
参数分别设为 0,10,20,30...90即可(首页的offset值为0)。获取到不同的网页后使用正则表达式提取出我们要的信息,就可以得到TOP100电影信息了,可以使用多线程加速爬取。
爬取实现
爬取首页
实现get_page()
方法,传入url
参数可以将抓取的页面结果返回。以下代码获取首页内容:
import requests from requests.exceptions import RequestException def get_page(url): headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36' } response = requests.get(url,headers = headers) try: if response.status_code == 200: return response.text return None except RequestException: print("request error") return None def main(): html = get_page('https://maoyan.com/board/4') print(html) main()
运行之后就成功获取到了首页的源代码,接下来使用正则表达式进行解析,提取出我们想要的信息。
正则提取
回到浏览器页面,在开发者工具Network监听组件中查看源代码。如图:
值得注意的是这里不是从Elements选项卡里查看的源代码,因为Elements里看到的源代码很有可能经过Javascript处理过从而和原始请求不同,所以要从Network选项卡里查看原始请求得到的源码。
查看此处代码:
不难发现,要爬取的每部电影信息都在<dd>
标签里,接下来使用正则表达式提取信息。
首先,提取它的排名信息,它的排名信息在class
为board-index
的i
标签里,使用非贪婪匹配来提取i
内的信息,正则表达式可以写为:<dd>.*?board-index.*?>(\d+)</i>
接下来提取电影的封面图片。在排名后面的a
便签里有两个img
便签,经过检查,第二个img
是电影的封面图片,正则:.*?data-src="(.*?)"
然后提取电影的名称,它在class
为name
的<p>
便签内,可以使用name
作为标志位进一步提取到其内a
的文本内容,正则写为:.*?name.*?a.*?>(.*?)</a>
提取主演:.*?star">(.*?)</p>
提取上映时间:.*?releasetime">(.*?)</p>
提取评分:.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>
最后正则表达式写为:<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>
上面的正则表达式可以匹配一个电影,匹配了7条信息,接下来可以通过findall()
方法提取所有内容。可以定义一个用来解析页面的方法parse_page()
,代码如下:
def parse_page(html): pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star">(.*?)</p>' + '.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',r.S) #re.S使.能匹配任意字符 items = pattern.findall(str(html))
这样就成功得拿到了一页10个电影的信息,这是一个列表,获取到的结果如下:
[('1', 'https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c', '霸王别姬', '\n 主演:张国荣,张丰毅,巩俐\n ', '上映时间:1993-01-01', '9.', '6'), ('2', 'https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@160w_220h_1e_1c', '肖申克的救赎', '\n 主演:蒂姆·罗宾斯,摩 根·弗里曼,鲍勃·冈顿\n ', '上映时间:1994-10-14(美国)', '9.', '5'), ('3', 'https://p0.meituan.net/movie/54617769d96807e4d81804284ffe2a27239007.jpg@160w_220h_1e_1c', '罗 马假日', '\n 主演:格利高里·派克,奥黛丽·赫本,埃迪·艾伯特\n ', '上映时间:1953-09-02(美国)', '9.', '1'), ('4', 'https://p0.meituan.net/movie/e55ec5d18ccc 83ba7db68caae54f165f95924.jpg@160w_220h_1e_1c', '这个杀手不太冷', '\n 主演:让·雷诺,加里·奥德曼,娜塔莉·波特曼\n ', '上映时间:1994-09-14(法国)', '9.', ' 5'), ('5', 'https://p1.meituan.net/movie/f5a924f362f050881f2b8f82e852747c118515.jpg@160w_220h_1e_1c', '教父', '\n 主演:马龙·白兰度,阿尔·帕西诺,詹姆斯·肯恩\n ', '上映时间:1972-03-24(美国)', '9.', '3'), ('6', 'https://p1.meituan.net/movie/0699ac97c82cf01638aa5023562d6134351277.jpg@160w_220h_1e_1c', '泰坦尼克号', '\n 主演:莱昂纳多·迪卡普里奥,凯特·温丝莱特,比利·赞恩\n ', '上映时间:1998-04-03', '9.', '5'), ('7', 'https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e04110 8.jpg@160w_220h_1e_1c', '唐伯虎点秋香', '\n 主演:周星驰,巩俐,郑佩佩\n ', '上映时间:1993-07-01(中国香港)', '9.', '2'), ('8', 'https://p0.meituan.net/movie/b076ce63e9860ecf1ee9839badee5228329384.jpg@160w_220h_1e_1c', '千与千寻', '\n 主演:柊瑠美,入野自由,夏木真理\n ', '上映时间:2001-07-20(日本)', '9.', '3'), ('9', 'https://p0.meituan.net/movie/46c29a8b8d8424bdda7715e6fd779c66235684.jpg@160w_220h_1e_1c', '魂断蓝桥', '\n 主演:费雯·丽,罗伯特·泰勒,露塞尔·沃特森\n ', '上映时间:1940-05-17(美国)', '9.', '2'), ('10', 'https://p0.meituan.net/movie/230e71d398e0c54730d58dc4bb6e4cca51662.jpg@160w_220h_1e_1c', '乱世佳人', '\n 主演:费雯·丽,克拉克·盖博,奥利维娅·德哈维兰\n ', '上映时间:1939-12-15(美国)', '9.', '1')]
这样的数据看上去很杂乱,使用字典将数据格式化:
for item in items: yield { 'top':item[0], 'image_src':item[1], 'name':item[2], 'actor':item[3].strip()[3:] if len(item[3]) > 3 else '', 'releasetime':item[4].strip()[5:], 'score':item[5] + item[6] }
这样就可以获得电影信息的结构化数据了,每个电影的信息都包含在一个字典里。获得的结果如下:
{'top': '1', 'image_src': 'https://p1.meituan.net/movie/20803f59291c47e1e116c11963ce019e68711.jpg@160w_220h_1e_1c', 'name': '霸王别姬', 'actor': '张国荣,张丰毅,巩俐', 'releasetime': '1993-01-01', 'score': '9.6'} {'top': '2', 'image_src': 'https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@160w_220h_1e_1c', 'name': '肖申克的救赎', 'actor': '蒂姆·罗宾斯,摩根·弗里曼,鲍勃·冈顿', 'releasetime': '1994-10-14(美国)', 'score': '9.5'} {'top': '3', 'image_src': 'https://p0.meituan.net/movie/54617769d96807e4d81804284ffe2a27239007.jpg@160w_220h_1e_1c','name': '罗马假日', 'actor': '格利高里·派克,奥黛丽·赫本,埃迪·艾伯特', 'releasetime': '1953-09-02(美国)', 'score': '9.1'} {'top': '4', 'image_src': 'https://p0.meituan.net/movie/e55ec5d18ccc83ba7db68caae54f165f95924.jpg@160w_220h_1e_1c', 'name': '这个杀手不太冷', 'actor': '让·雷诺,加里·奥德曼,娜塔莉·波特曼', 'releasetime': '1994-09-14(法国)', 'score': '9.5'} {'top': '5', 'image_src': 'https://p1.meituan.net/movie/f5a924f362f050881f2b8f82e852747c118515.jpg@160w_220h_1e_1c', 'name': '教父', 'actor': '马龙·白兰度,阿尔·帕西诺,詹姆斯·肯恩', 'releasetime': '1972-03-24(美国)', 'score': '9.3'} {'top': '6', 'image_src': 'https://p1.meituan.net/movie/0699ac97c82cf01638aa5023562d6134351277.jpg@160w_220h_1e_1c', 'name': '泰坦尼克号', 'actor': '莱昂纳多·迪卡普里奥,凯特·温丝莱特,比利·赞恩', 'releasetime': '1998-04-03', 'score': '9.5'} {'top': '7', 'image_src': 'https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@160w_220h_1e_1c', 'name': '唐伯虎点秋香', 'actor': '周星驰,巩俐,郑佩佩', 'releasetime': '1993-07-01(中国香港)', 'score': '9.2'} {'top': '8', 'image_src': 'https://p0.meituan.net/movie/b076ce63e9860ecf1ee9839badee5228329384.jpg@160w_220h_1e_1c', 'name': '千与千寻', 'actor': '柊瑠美,入野自由,夏木真理', 'releasetime': '2001-07-20(日本)', 'score': '9.3'} {'top': '9', 'image_src': 'https://p0.meituan.net/movie/46c29a8b8d8424bdda7715e6fd779c66235684.jpg@160w_220h_1e_1c', 'name': '魂断蓝桥', 'actor': '费雯·丽,罗伯特·泰勒,露塞尔·沃特森', 'releasetime': '1940-05-17(美国)', 'score': '9.2'} {'top': '10', 'image_src': 'https://p0.meituan.net/movie/230e71d398e0c54730d58dc4bb6e4cca51662.jpg@160w_220h_1e_1c', 'name': '乱世佳人', 'actor': '费雯·丽,克拉克·盖博,奥利维娅·德哈维兰', 'releasetime': '1939-12-15(美国)', 'score': '9.1'}
写入文件
得到数据后最后将数据保存到文件,通过JOSN库的dumps()
方法可以实现字典的序列化。因为这里要处理中文,将ensure_ascii
参数设为False
就可以保证输出结果是中文形式而不是Unicode编码。代码如下:
def write_to_file(content): with open('result.txt','a',encoding='utf-8') as f: f.write(json.dumps(content,ensure_ascii = False) + '\n') f.close()
其中open()
指定写入方式为a
尾部写入,这是因为此时是for
循环写入数据,如果用w
写入只会保留最后一组的数据。或者在这之前打开文件,等写入完数据后再关闭也可以。
通过调用write_to_file()
方法即可实现将字典写入到文本文件的过程。
main方法
实现main()
方法接收一个offset值作为偏移量,然后构造URL进行爬取。代码如下:
def main(offset): url = "http://maoyan.com/board/4?offset=" + str(offset) html = get_page(url) for item in parse_page(html): print(item) write_of_file(item)
多线程分页爬取
上面实现了给main()
传入一个offset值爬取单页10个电影的数据,接下来使用多线程来抓取整个TOP100的电影数据。
from multiprocessing import Pool # 引入多线程模块 if __name__ == '__main__': #创建线程池 pool = Pool() # pool.map第一个参数是函数,第二个参数是传递给函数的参数 pool.map(main,[i*10 for i in range(10)])
Pool.map()
函数第一个参数是函数,第二个参数是传递给函数的参数,在上面代码中是一个迭代器,将迭代器中的数字作为参数依次传入函数中。
注意:使用多线程爬取会导致最后写入到文件内的电影数据(top值)是乱序的,如需保证爬取到的电影信息写入到文件是按照top值排序的,放弃多线程将代码改为:
import time #引入时间模块 if __name__ == '__main__': for i in range(10): main(offset=i * 10) time.sleep(1)
为突破猫眼反爬虫机制(速度过快会无响应),上面代码增加了一个延时等待。
大功告成!完整代码如下:
import requests import re import time import json from requests.exceptions import RequestException from multiprocessing import Pool def get_page(url): headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36' } response = requests.get(url,headers = headers) try: if response.status_code == 200: return response.text return None except RequestException: print("request error") return None def parse_page(html): pattern = re.compile('<dd>.*?board-index.*?>(\d+)</i>.*?data-src="(.*?)".*?name.*?a.*?>(.*?)</a>.*?star">(.*?)</p>' + '.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>.*?</dd>',re.S) #re.S使.能匹配任意字符 items = pattern.findall(str(html)) for item in items: yield { 'top':item[0], 'image_src':item[1], 'name':item[2], 'actor':item[3].strip()[3:] if len(item[3]) > 3 else '', 'releasetime':item[4].strip()[5:], 'score':item[5] + item[6] } def write_to_file(content): with open('result.txt','a',encoding='utf-8') as f: f.write(json.dumps(content,ensure_ascii = False) + '\n') f.close() def main(offset): url = "http://maoyan.com/board/4?offset=" + str(offset) html = get_page(url) for item in parse_page(html): print(item) write_to_file(item) # 如需保证电影顺序,则放弃使用多线程 # if __name__ == '__main__': # for i in range(10): # main(offset=i * 10) # time.sleep(1) if __name__ == '__main__': pool = Pool() pool.map(main,[i*10 for i in range(10)])
本文中的代码地址:https://github.com/grisse/Cra...