在本篇博客中,我们将使用requests+正则表达式来爬取猫眼电影官网的TOP100电影榜单,获取每部电影的序号,片名,主演,上映日期,评分和封面等内容。
之前在Python爬虫实战(1)中我们曾爬取过,本篇博客将对上次内容进行升级,使用yield和多线程。
打开猫眼Top100,分析URL的变化:发现Top100榜总共包含10页,每页10部电影,并且每一页的URL都是有规律的,如第2页为https://maoyan.com/board/4?offset=10,第三页为https://maoyan.com/board/4?offset=20。由此可得第n页为https://maoyan.com/board/4?offset=(n-1)*10。
def get_one_page(url):
try:
headers={
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
response = requests.get(url,headers=headers)
if response.status_code == 200:
return response.text
return None
except RequestException:
return None
打开浏览器,右键检查,在element选项卡中,我们发现每部电影都在一对dd标签中:
接下来,我们可以用正则表达式进行提取,用.*?略过没用的部分,保存一些关键部分进行定位,用(.*?)获取想要的信息:
def parse_one_page(html):
pattern = re.compile('.*?board-index.*?>(\d+)' #序号
+'.*?data-src="(.*?)"'#封面图片
+'.*?name.*?(.*?)' #片名
+'.*?star.*?>(.*?)' #主演
+'.*?releasetime.*?>(.*?)' #上映时间
+'.*?score.*?integer.*?>(.*?)' #评分整数部分
+'.*?fraction.*?>(.*?)' #评分小数部分
,re.S)
items = pattern.findall(html)
#之前是把每个item保存在字典里,在追加到一个列表中。
#现在对于每个item 用yield声明一个迭代器
for item in items:
yield {
'index':item[0],
'logo':item[1],
'title':item[2],
'star':item[3].strip()[3:] if len(item[3])>3 else '', #去除 前后空白字符 和 主演:
'releasetime':item[4].strip()[5:] if len(item[4])>5 else '', #去除 前后空白字符 和 上映时间:
'score':item[5].strip()+item[6].strip() #去除前后空白字符 拼接评分整数和小数部分
}
我们把数据直接存储到文本文件中。
def write_to_file(content):
with open('result.txt','a',encoding='utf-8') as f:
f.write(json.dumps(content,ensure_ascii=False)+'\n') #ensure_ascii=False 可以显示中文字符
def main(offset):
url = 'https://maoyan.com/board/4?offset='+str(offset) #新页面url
html = get_one_page(url) #获取页面html
#print(html)
for item in parse_one_page(html): #由于解析函数返回的是一个迭代器 所以需要使用for循环对其进行实体化
print(item)
write_to_file(item) #把每个item写入文件
if __name__ == '__main__':
'''
for i in range(10):
main(i*10)
time.sleep(1)
'''
pool = Pool()
pool.map(main,[x*10 for x in range(10)])
正常情况下使用注释掉的部分,对10个页面顺序进行爬取。如果想加速,实现秒抓,可以添加多线程机制,将待爬取的页面分配给多个线程,并行爬取。建立起main函数及其传入参数之间的映射即可。
import requests
from requests import RequestException
import re
import json
from multiprocessing.pool import Pool
def get_one_page(url):
try:
headers={
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36'
}
response = requests.get(url,headers=headers)
if response.status_code == 200:
return response.text
return None
except RequestException:
return None
def parse_one_page(html):
pattern = re.compile('.*?board-index.*?>(\d+)' #序号
+'.*?data-src="(.*?)"'#封面图片
+'.*?name.*?(.*?)' #片名
+'.*?star.*?>(.*?)' #主演
+'.*?releasetime.*?>(.*?)' #上映时间
+'.*?score.*?integer.*?>(.*?)' #评分整数部分
+'.*?fraction.*?>(.*?)' #评分小数部分
,re.S)
items = pattern.findall(html)
#之前是把每个item保存在字典里,在追加到一个列表中。
#现在对于每个item 用yield声明一个迭代器
for item in items:
yield {
'index':item[0],
'logo':item[1],
'title':item[2],
'star':item[3].strip()[3:] if len(item[3])>3 else '', #去除 前后空白字符 和 主演:
'releasetime':item[4].strip()[5:] if len(item[4])>5 else '', #去除 前后空白字符 和 上映时间:
'score':item[5].strip()+item[6].strip() #去除前后空白字符 拼接评分整数和小数部分
}
def write_to_file(content):
with open('result.txt','a',encoding='utf-8') as f:
f.write(json.dumps(content,ensure_ascii=False)+'\n') #ensure_ascii=False 可以显示中文字符
def main(offset):
url = 'https://maoyan.com/board/4?offset='+str(offset) #新页面url
html = get_one_page(url) #获取页面html
#print(html)
for item in parse_one_page(html): #由于解析函数返回的是一个迭代器 所以需要使用for循环对其进行实体化
print(item)
write_to_file(item) #把每个item写入文件
if __name__ == '__main__':
'''
for i in range(10):
main(i*10)
time.sleep(1)
'''
pool = Pool()
pool.map(main,[x*10 for x in range(10)])
通过观察结果会发现,使用多线程并行爬取后,并不是按顺序爬取的,index并不是连续的。说明每个线程都在独立并行的爬取分配给它的页面。
如果不使用多线程,结果将是顺序爬取的。