requests+正则表达式+multiprocessing多线程抓取猫眼电影TOP100

本文介绍利用Requests库、multiprocessing库和正则表达式爬取猫眼电影TOP100电影的相关信息,提取出电影名称、上映时间、评分、封面图片等信息,将爬取的内容写入到文件中。站点URL为 http://maoyan.com/board/4

准备

本文使用了Requests库,使用pip安装: pip install requests

分析

打开http://maoyan.com/board/4,可以看到榜单信息。如下图所示
requests+正则表达式+multiprocessing多线程抓取猫眼电影TOP100

排名第一的电影是霸王别姬,可以提取的信息有电影名称、主演、上映时间、评分、封面图等。
点击页面下方的分页列表翻页到第二页,会发现URL会变成https://maoyan.com/board/4?offset=10,比首页多了个offset=10 参数,而目前显示的是排名11-20的电影,初步判断这是偏移量参数。再点击下一页,URL变成了https://maoyan.com/board/4?offset=20offset变成了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监听组件中查看源代码。如图:
requests+正则表达式+multiprocessing多线程抓取猫眼电影TOP100
值得注意的是这里不是从Elements选项卡里查看的源代码,因为Elements里看到的源代码很有可能经过Javascript处理过从而和原始请求不同,所以要从Network选项卡里查看原始请求得到的源码。

查看此处代码:
requests+正则表达式+multiprocessing多线程抓取猫眼电影TOP100
requests+正则表达式+multiprocessing多线程抓取猫眼电影TOP100
不难发现,要爬取的每部电影信息都在<dd>标签里,接下来使用正则表达式提取信息。

首先,提取它的排名信息,它的排名信息在classboard-indexi标签里,使用非贪婪匹配来提取i内的信息,正则表达式可以写为:<dd>.*?board-index.*?>(\d+)</i>

接下来提取电影的封面图片。在排名后面的a便签里有两个img便签,经过检查,第二个img是电影的封面图片,正则:.*?data-src="(.*?)"

然后提取电影的名称,它在classname<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...

相关推荐