# 引入各个模块的作用说明
import requests # 访问URL的工具
import logging # 以日志形式进行结果输出
import re # 引入正则表达式
from urllib.parse import urljoin # 实现URL的拼接
import json # 存储数据
from os import makedirs # 创建用来存储数据的文件夹
from os.path import exists # 判断文件夹是否存在,辅助文件夹的创建
import multiprocessing # 多线程爬取,加快爬取速度
logging.basicConfig(level=logging.INFO,
format='%(asctime)s-%(levelname)s:%(message)s') # 设置日志输出模式
BASE_URL = 'https://ssr1.scrape.center' # 设置基本URL
TOTAL_PAGE = 10 # 目标网站共十页
RESULTS_DIR = 'results' # 创建名为results的文件夹
exists(RESULTS_DIR) or makedirs(RESULTS_DIR)
# 功能:返回对应URL的前端代码
# 参数:URL
# 返回值类型:string
def scrape_page(url):
logging.info('scraping %s...', url)
try:
response = requests.get(url) # 对目标URL进行访问
if response.status_code == 200: # 对状态码进行判断,如果返回状态码200说明连接正常
return response.text
logging.error('get invalid status code %s while scraping %s',
response.staus_code, url)
except requests.RequestException: # 对错误的堆栈信息进行追踪
logging.error('error occurred while scraping %s', url,
exc_info=True)
# 功能:返回对应URL的前端代码
# 参数:网站当前页数
# 返回值类型:string
def scrape_index(page):
index_url = f'{BASE_URL}/page/{page}' # 'f'参数使得变量可以填充字符串
return scrape_page(index_url)
# 功能:获取每部电影对应的URL
# 参数:网页的前端代码
# 返回值类型:string
def parse_index(html):
pattern = re.compile('<a.*?href="(.*?)".*?class="name">') # 用re构造出每部电影对应href的匹配模式
items = re.findall(pattern, html) # 在前端代码中查找与pattern相匹配的代码
if not items:
return []
for item in items:
detail_url = urljoin(BASE_URL, item) # 观察每部电影URL可知,URL = 基本URL + items
logging.info('get detail url %s', detail_url)
yield detail_url # 关键字yield的机制是,暂停当前运行进程、输出当前结果并保存状态
# 此函数作用同scrape_page()完全相同
# 构造此函数的目的:为日后进行爬取拓展提供接口
def scrape_detail(url):
return scrape_page(url)
# 功能:定位目标资源的前端代码,而后返回相应的数据
# 参数:对应每部电影的前端代码
# 返回值类型:dictionary
def parse_detail(html):
# 分别对电影的封面、名称、分类、发行时间、评分、概要设置相应的re匹配规则
cover_pattern = re.compile('class="item.*?<img.*?src="(.*?)".*?class="cover">', re.S) # Make '.' match all characters
name_pattern = re.compile('<h2.*?>(.*?)</h2>')
categories_pattern = re.compile('<button.*?category.*?<span>(.*?)</span>.*?</button>', re.S)
published_at_pattern = re.compile('(\d{4}-\d{2}-\d{2})\s上映') # As for time, use standard (\d{4}-\d{2}-\d{2})
score_pattern = re.compile('<p.*?score.*?>(.*?)</p>', re.S)
drama_pattern = re.compile('<div.*?drama.*?>.*?<p.*?>(.*?)</p>', re.S)
# 在前端代码中查找相应的匹配数据
cover = re.search(cover_pattern, html).group(1).strip() # strip delete space in start and end part
name = re.search(name_pattern, html).group(1).strip()
categories = re.findall(categories_pattern, html)
published_at = re.search(published_at_pattern, html).group(1) if re.search(published_at_pattern, html) else None
score = float(re.search(score_pattern, html).group(1).strip())
drama = re.search(drama_pattern, html).group(1).strip()
# 将所获取的数据以字典的形式进行返回
return {
'cover': cover,
'name': name,
'categories': categories,
'published_at': published_at,
'drama': drama,
'score': score
}
# 功能:将获取到的数据以.json形式存储
# 参数:数据
# 返回值类型:None
def save_data(data):
name = data.get('name') # 获取电影名称
data_path = f'{RESULTS_DIR}/{name}.json' # 根据电影名称,规定相应电影的文件名
json.dump(data, open(data_path, 'w', encoding='utf-8'),
ensure_ascii=False, indent=2) # 将数据写入文件
# 功能:爬取并保存电影信息
# 参数:网站当前页数
# 返回值类型:None
def main(page):
index_html = scrape_index(page) # 获取当前页面下的前端代码
detail_urls = parse_index(index_html) # 获取当前页面下所有电影的URL
for detail_url in detail_urls:
detail_html = scrape_detail(detail_url) # 获取每部电影页面的前端代码
data = parse_detail(detail_html) # 获取每部电影的信息
logging.info('get detail data %s', data)
logging.info('save data to json data')
save_data(data) # 将获取到的信息进行存储
logging.info('data saved successfully')
if __name__ == '__main__':
pool = multiprocessing.Pool(processes=4) # 构造进程池,并将进程数设为4
pages = range(1, TOTAL_PAGE + 1) # 构造页面数
pool.map(main, pages) # 将 pages 作为参数传入到 main() 当中,
# 并将每一次 main() 函数调用作为一个进程,
# 加入到进程池当中
pool.close() # 等待worker进程结束再关闭进程池
pool.join() # 防止主程序在worker进程结束前结束