# 导入所需库
import requests # 用于发送HTTP请求
import logging # 用于记录日志
import re # 用于正则表达式匹配
from urllib.parse import urljoin # 用于拼接URL
import json # 用于JSON数据处理
from os import makedirs # 创建目录
from os.path import exists # 检查路径是否存在
import multiprocessing # 用于多进程处理
# 基础URL和总页数常量
BASE_URL = 'https://ssr1.scrape.center' # 目标网站根URL
TOTAL_PAGE = 10 # 需要爬取的总页数
# 定义一个用于保存爬取结果的目录名,所有 JSON 文件将保存在这个文件夹中
RESULTS_DIR = 'results'
# 如果该目录不存在,则创建它(避免手动提前建目录)
# 使用短路逻辑:如果 exists(RESULTS_DIR) 为 False,就会执行 makedirs(RESULTS_DIR)
exists(RESULTS_DIR) or makedirs(RESULTS_DIR)
def save_data(data):
"""保存数据到JSON文件"""
name = data.get('name') # 从数据中获取电影名称作为文件名
data_path = f'{RESULTS_DIR}/{name}.json' # 构建文件路径
# 写入JSON文件(确保非ASCII字符正常显示)
json.dump(data, open(data_path, 'w', encoding='utf-8'),
ensure_ascii=False, indent=2)
# 配置日志格式和级别
logging.basicConfig(
level=logging.INFO, # 设置日志级别为INFO
format='%(asctime)s-%(levelname)s:%(message)s' # 日志格式:时间-级别-消息
)
def scrape_page(url):
"""通用页面爬取函数"""
logging.info('scraping %s...', url)
try:
response = requests.get(url) # 发送GET请求
if response.status_code == 200: # 如果响应成功
return response.text # 返回HTML内容
# 记录错误状态码
logging.error('get invalid status code %s while scraping %s',
response.status_code, url)
except requests.RequestException:
# 记录请求异常信息(包含堆栈跟踪)
logging.error('error occurred while scraping %s', url, exc_info=True)
def scrape_index(page):
"""列表页爬取函数"""
index_url = f'{BASE_URL}/page/{page}' # 构造列表页URL
return scrape_page(index_url) # 调用通用爬取方法
def parse_index(html):
"""解析列表页HTML,提取详情页URL"""
pattern = re.compile('<a.*?href="(.*?)".*?class="name">') # 匹配详情页链接的正则
items = re.findall(pattern, html) # 查找所有匹配项
if not items:
return [] # 无匹配返回空列表
for item in items:
detail_url = urljoin(BASE_URL, item) # 拼接完整URL
logging.info('get detail url %s', detail_url)
yield detail_url # 生成器逐个返回URL
def scrape_detail(url):
"""详情页爬取函数(直接复用通用爬取)"""
return scrape_page(url)
def parse_detail(html):
"""解析详情页HTML,提取电影信息"""
# 定义各类信息的正则表达式模式
cover_pattern = re.compile('class="item".*?<img.*?src="(.*?)".*?class="cover">', re.S)
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?上映')
drama_pattern = re.compile('<div.*?drama.*?>.*?<p.*?>(.*?)</p>', re.S)
score_pattern = re.compile('<p.*?score.*?>(.*?)</p>', re.S)
# 提取并处理各项数据(处理可能的匹配失败)
cover = re.search(cover_pattern, html).group(1).strip() if re.search(cover_pattern, html) else None
name = re.search(name_pattern, html).group(1).strip() if re.search(name_pattern, html) else None
categories = re.findall(categories_pattern, html) if re.findall(categories_pattern, html) else []
published_at = re.search(published_at_pattern, html).group(1) if re.search(published_at_pattern, html) else None
drama = re.search(drama_pattern, html).group(1).strip() if re.search(drama_pattern, html) else None
score = float(re.search(score_pattern, html).group(1).strip()) if re.search(score_pattern, html) else None
# 返回结构化数据字典
return {
'cover': cover, # 封面图URL
'name': name, # 电影名称
'categories': categories, # 分类标签列表
'published_at': published_at, # 上映日期
'drama': drama, # 剧情简介
'score': score # 评分(浮点数)
}
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('saving data to json data')
save_data(data) # 保存数据到文件
logging.info('data saved successfully')
if __name__ == '__main__':
"""多进程执行入口"""
pool = multiprocessing.Pool() # 创建进程池
pages = range(1, TOTAL_PAGE + 1) # 生成页码列表 (1-10)
pool.map(main, pages) # 多进程并行处理所有页面
pool.close() # 关闭进程池(停止添加新任务)
pool.join() # 等待所有子进程完成