Python 爬虫入门:从数据爬取到转存 MySQL 数据库

发布于:2025-06-26 ⋅ 阅读:(21) ⋅ 点赞:(0)

前言

在本篇博客中,我们将介绍一个基础的 Python 爬虫项目,包括使用 requestsBeautifulSoup 进行网页数据爬取,并将获取的数据存储到 MySQL 数据库中。该项目适合初学者了解网络爬虫的基本流程以及如何将数据持久化存储。


一、项目目标

  • 学习使用 requests 发起 HTTP 请求获取网页内容。
  • 使用 BeautifulSoup 解析 HTML 页面并提取数据。
  • 将提取的数据保存到 MySQL 数据库中。
  • 掌握基本的数据库连接与操作方法。

二、环境准备

所需模块安装:

pip install requests beautifulsoup4 mysql-connector-python
  • requests: 用于发送 HTTP 请求。
  • beautifulsoup4: 用于解析 HTML 文档。
  • mysql-connector-python: 用于连接和操作 MySQL 数据库。

三、爬取目标网站示例

我们以爬取 豆瓣电影 Top250 页面为例,抓取以下信息:

  • 电影名称
  • 导演/主演
  • 上映年份
  • 豆瓣评分

四、代码实现与讲解

1. 网页爬取部分(使用 requests)

import requests
from bs4 import BeautifulSoup
import time

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0 Safari/537.36'
}

def fetch_page(url):
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.text
    else:
        print(f"请求失败,状态码:{response.status_code}")
        return None

说明:

  • 使用 requests.get() 获取网页内容。
  • 设置 headers 模拟浏览器访问,防止被反爬机制屏蔽。
  • 若返回状态码为 200,则返回 HTML 内容。

2. 数据解析部分(使用 BeautifulSoup)

def parse_html(html):
    soup = BeautifulSoup(html, 'html.parser')
    movie_list = []

    for item in soup.find_all('div', class_='item'):
        rank = item.find('em').text.strip()
        title = item.find('span', class_='title').text.strip()
        info = item.find('div', class_='bd').p.text.strip().split('\n')[0]
        year = item.find('span', class_='year').text.strip()
        rating = item.find('span', class_='rating_num').text.strip()

        movie_list.append({
            'rank': rank,
            'title': title,
            'info': info,
            'year': year,
            'rating': rating
        })

    return movie_list

说明:

  • 使用 BeautifulSoup 解析 HTML 结构。
  • 查找每个电影条目 <div class="item">
  • 提取电影排名、名称、导演/演员信息、年份、评分等字段。
  • 将每部电影的信息以字典形式添加到列表中。

3. 存储到 MySQL 数据库部分

import mysql.connector

def save_to_mysql(data):
    conn = mysql.connector.connect(
        host='localhost',
        user='root',
        password='yourpassword',
        database='douban_movies'
    )
    cursor = conn.cursor()

    # 创建表(如果不存在)
    create_table_sql = """
    CREATE TABLE IF NOT EXISTS top250 (
        id INT AUTO_INCREMENT PRIMARY KEY,
        rank VARCHAR(10),
        title VARCHAR(255),
        info TEXT,
        year VARCHAR(10),
        rating VARCHAR(10)
    )
    """
    cursor.execute(create_table_sql)

    # 插入数据
    insert_sql = """
    INSERT INTO top250 (rank, title, info, year, rating)
    VALUES (%s, %s, %s, %s, %s)
    """
    for movie in data:
        cursor.execute(insert_sql, (
            movie['rank'],
            movie['title'],
            movie['info'],
            movie['year'],
            movie['rating']
        ))

    conn.commit()
    cursor.close()
    conn.close()
    print("数据已成功插入数据库!")

说明:

  • 使用 mysql.connector.connect() 连接本地 MySQL 数据库。
  • 使用 SQL 建表语句创建 top250 表(若不存在)。
  • 使用参数化 SQL 插入数据,避免 SQL 注入。
  • 循环遍历所有电影数据并插入数据库。

4. 主程序入口

def main():
    all_movies = []
    base_url = "https://movie.douban.com/top250?start={}&filter="

    for i in range(0, 250, 25):  # 爬取前10页
        url = base_url.format(i)
        html = fetch_page(url)
        if html:
            movies = parse_html(html)
            all_movies.extend(movies)
            print(f"第 {i // 25 + 1} 页数据抓取完成,共 {len(movies)} 条记录")
            time.sleep(2)  # 避免频繁请求

    # 存储到数据库
    save_to_mysql(all_movies)

if __name__ == '__main__':
    main()

说明:

  • 构造分页 URL 地址,循环爬取多页数据。
  • 每次请求后暂停 2 秒,防止被封 IP。
  • 最后统一将所有数据插入数据库。

五、数据库结构设计

CREATE DATABASE douban_movies;

USE douban_movies;

CREATE TABLE top250 (
    id INT AUTO_INCREMENT PRIMARY KEY,
    rank VARCHAR(10),
    title VARCHAR(255),
    info TEXT,
    year VARCHAR(10),
    rating VARCHAR(10)
);

六、运行结果示例

第 1 页数据抓取完成,共 25 条记录
第 2 页数据抓取完成,共 25 条记录
...
第 10 页数据抓取完成,共 25 条记录
数据已成功插入数据库!

MySQL 中的 top250 表将包含如下字段:

id rank title info year rating
1 1 肖申克的救赎 导演: 弗兰克·德拉邦特 1994 9.7
... ... ... ... ... ...

七、注意事项

  1. 反爬策略:实际部署时应设置合理的请求间隔,必要时使用代理 IP。
  2. 异常处理:建议增加异常捕获逻辑,如超时重试、页面解析错误等。
  3. 编码问题:注意网页编码是否为 UTF-8,必要时进行转换。
  4. 数据库安全:生产环境中应使用配置文件管理数据库账号密码,避免硬编码。

注:当然这个基础爬虫代码非常适合初学者入门,但在实际应用中还存在一些明显的缺点和潜在问题。下面我将从几个方面分析其不足之处,并给出相应的补偿措施或优化建议

八、代码缺点讲解

1. 缺乏异常处理机制

问题:
  • 网络请求可能失败(如超时、403/404 错误);
  • 页面结构变化可能导致解析失败;
  • 数据库连接失败或插入出错未做捕获。
补偿措施:
  • 使用 try-except 捕获异常;
  • 添加重试机制;
  • 日志记录错误信息以便排查。
def fetch_page(url):
    try:
        response = requests.get(url, headers=headers, timeout=10)
        if response.status_code == 200:
            return response.text
        else:
            print(f"请求失败,状态码:{response.status_code}")
            return None
    except requests.RequestException as e:
        print(f"请求异常:{e}")
        return None

2. 数据解析依赖页面结构,稳定性差

问题:
  • 如果目标网站更新了 HTML 结构,解析会失败;
  • 没有做字段缺失判断,容易抛出 AttributeError
补偿措施:
  • 使用 .find() 或 .select() 前判断是否存在;
  • 使用 get_text(strip=True) 替代 .text.strip() 提高容错;
  • 对关键字段使用默认值。
title_tag = item.find('span', class_='title')
title = title_tag.text.strip() if title_tag else '未知标题'

3. 数据库操作效率低

问题:
  • 每次插入都建立一次数据库连接,效率低下;
  • 插入数据没有批量处理,速度慢。
补偿措施:
  • 将数据库连接提取到函数外,复用连接;
  • 使用 executemany() 批量插入数据。
def save_to_mysql(data):
    conn = mysql.connector.connect(...)
    cursor = conn.cursor()

    insert_sql = """
    INSERT INTO top250 (rank, title, info, year, rating)
    VALUES (%s, %s, %s, %s, %s)
    """

    values = [
        (movie['rank'], movie['title'], movie['info'], movie['year'], movie['rating'])
        for movie in data
    ]

    cursor.executemany(insert_sql, values)
    conn.commit()
    ...

4. 无去重机制

问题:
  • 若多次运行脚本,会导致重复插入相同数据;
  • 缺乏唯一性校验或更新机制。
补偿措施:
  • 在数据库表中添加唯一索引(如电影名称 + 年份);
  • 插入前先查询是否已存在该条目;
  • 或者采用 INSERT IGNORE / ON DUPLICATE KEY UPDATE
ALTER TABLE top250 ADD UNIQUE (title, year);
insert_sql = """
INSERT IGNORE INTO top250 (rank, title, info, year, rating)
VALUES (%s, %s, %s, %s, %s)
"""

5. 反爬应对能力弱

问题:
  • 固定 User-Agent 易被识别为爬虫;
  • 请求频率固定,容易触发封禁;
  • 没有使用代理 IP。
补偿措施:
  • 使用随机 User-Agent;
  • 设置随机延迟;
  • 配置代理 IP 列表轮换使用;
  • 使用 fake_useragent 库生成真实 UA。
from fake_useragent import UserAgent

ua = UserAgent()
headers = {'User-Agent': ua.random}
import random
time.sleep(random.uniform(1, 3))  # 随机延迟 1~3 秒

6. 日志和调试信息不足

问题:
  • 出现问题难以定位;
  • 缺乏进度跟踪与详细输出。
补偿措施:
  • 使用 Python 的 logging 模块记录日志;
  • 记录请求 URL、响应状态、插入数量等关键信息。
import logging

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 示例
logging.info("开始抓取第 {} 页".format(page_num))

7. 配置信息硬编码在代码中

问题:
  • 修改数据库账号密码需要修改源码;
  • 不利于部署多个环境(开发/测试/生产)。
补偿措施:
  • 使用配置文件(如 config.ini 或 .env 文件);
  • 使用 dotenv 加载环境变量。
# config.ini
[mysql]
host = localhost
user = root
password = yourpassword
database = douban_movies
import configparser

config = configparser.ConfigParser()
config.read('config.ini')
db_config = config['mysql']

九、总结对比表

问题点 影响程度 优化建议
异常处理缺失 添加 try-except 和重试机制
页面结构变动 增加字段判空、健壮的解析逻辑
数据库效率低 复用连接 + 批量插入
数据重复 添加唯一索引 + 去重插入
反爬机制薄弱 随机 UA、IP 代理、延迟控制
日志不完善 使用 logging 模块
配置信息硬编码 使用配置文件或环境变量

十、进阶改进建议(可根据自身要求选择)

  • 使用 Scrapy 框架替代手动编写爬虫;
  • 使用 Selenium 对抗 JavaScript 渲染页面;
  • 使用 Redis 实现分布式爬虫;
  • 使用 Celery 进行异步任务调度;
  • 使用 SQLAlchemy 替代原生 SQL 提升 ORM 能力;
  • 使用 Docker 容器化部署爬虫项目。

如果你是刚入门的新手,这篇博客中的代码已经足够帮助你理解整个流程。但如果你想把这个代码用于生产环境或长期稳定运行,就需要根据上述分析进行优化和完善。

如果你希望我帮你把这段代码重构为一个更健壮、模块化的版本,也可以告诉我,我可以为你提供完整的重构方案和代码实现。

十一、总结

通过本教程,你已经大致掌握了:

  • 如何使用 requests 抓取网页数据;
  • 使用 BeautifulSoup 提取关键信息;
  • 使用 mysql-connector-python 将数据写入 MySQL;
  • 分页爬取、延迟请求等基本技巧。

这个项目是一个普通入门级爬虫案例,适用于大多数静态网页的数据采集需求。


如需进一步扩展,可以尝试:

  • 使用 Scrapy 框架构建更复杂的爬虫系统;
  • 将数据导出为 CSV 或 Excel 文件;
  • 可视化分析豆瓣电影评分分布等。

如果你喜欢这篇博客,欢迎点赞、收藏或留言交流!


网站公告

今日签到

点亮在社区的每一天
去签到