达永编程网

程序员技术分享与交流平台

爬取“https://ssr1.scrape.center”的爬虫

# 导入所需库
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()  # 等待所有子进程完成
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言