Python爬虫实战:爬取网易云课堂课程数据并存储到MySQL
在当今信息爆炸的时代,网络上有大量有价值的数据等待我们去挖掘和分析。本文将带你使用Python编写一个爬虫程序,爬取网易云课堂的Python课程数据,并将这些数据存储到MySQL数据库中。
项目概述
我们将构建一个完整的爬虫系统,主要功能包括:
爬取网易云课堂的Python课程信息解析课程数据并清洗将数据存储到MySQL数据库添加错误处理和日志记录
环境准备
在开始之前,确保你已经安装以下Python库:
import requests
import pymysql
import logging
import time
from typing import Dict, List, Optional
数据库设计
首先,我们需要设计一个合适的数据库表来存储课程信息:
CREATE TABLE IF NOT EXISTS course (
id INT PRIMARY KEY AUTO_INCREMENT,
course_id BIGINT UNIQUE,
product_name VARCHAR(255),
provider VARCHAR(100),
score DECIMAL(3,1),
learner_count INT,
lector_name VARCHAR(100),
original_price DECIMAL(10,2),
discount_price DECIMAL(10,2),
img_url VARCHAR(500),
big_img_url VARCHAR(500),
description TEXT,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
核心代码实现
1. 数据库连接配置
我们首先创建一个数据库连接类,提高代码的可维护性:
class MySQLManager:
def __init__(self, host="localhost", port=3306, user="root",
password="root", database="spider", charset="utf8"):
self.connection_params = {
"host": host,
"port": port,
"user": user,
"password": password,
"database": database,
"charset": charset
}
self.conn = None
self.cursor = None
def __enter__(self):
self.conn = pymysql.connect(**self.connection_params)
self.cursor = self.conn.cursor()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.cursor:
self.cursor.close()
if self.conn:
if exc_type is None:
self.conn.commit()
else:
self.conn.rollback()
self.conn.close()
2. 配置日志系统
添加日志记录可以帮助我们更好地调试和监控程序运行状态:
def setup_logging():
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('spider.log', encoding='utf-8'),
logging.StreamHandler()
]
)
3. 增强的爬取函数
我们对原有的爬取函数进行增强,添加错误重试机制和请求头随机化:
def get_json(page_index, retry_count=3):
"""获取课程JSON数据,支持重试机制"""
url = "https://study.163.com/p/search/studycourse.json"
payload = {
"activityId": 0,
"keyword": "python",
"orderType": 5,
"pageIndex": page_index,
"pageSize": 50,
"priceType": -1,
"qualityType": 0,
"relativeOffset": 0,
"searchTimeType": -1,
}
headers = {
"accept": "application/json",
"content-type": "application/json",
"origin": "https://study.163.com",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36",
"referer": f"https://study.163.com/courses?keyword=python&pageIndex={page_index}"
}
for attempt in range(retry_count):
try:
response = requests.post(url, json=payload, headers=headers, timeout=10)
response.raise_for_status() # 如果状态码不是200,抛出异常
content = response.json()
if content and content.get("code") == 0:
logging.info(f"成功获取第{page_index}页数据")
return content
else:
logging.warning(f"第{page_index}页返回数据异常: {content}")
except requests.exceptions.RequestException as e:
logging.error(f"第{page_index}页第{attempt+1}次请求失败: {str(e)}")
if attempt < retry_count - 1:
time.sleep(2) # 等待2秒后重试
else:
logging.error(f"第{page_index}页请求失败,已达最大重试次数")
except ValueError as e:
logging.error(f"第{page_index}页JSON解析失败: {str(e)}")
break
return None
4. 数据清洗和处理
在存储数据之前,我们需要对数据进行清洗和验证:
def clean_course_data(course_list):
"""清洗和验证课程数据"""
cleaned_courses = []
for item in course_list:
# 处理可能缺失的字段
course_id = item.get("courseId", 0)
product_name = item.get("productName", "").strip()
# 跳过无效数据
if not course_id or not product_name:
continue
# 构建课程数据元组,处理可能缺失的字段
course_value = (
0, # id,自增字段用0占位
course_id,
product_name,
item.get("provider", ""),
float(item.get("score", 0)), # 确保分数是浮点数
int(item.get("learnerCount", 0)), # 确保是整数
item.get("lectorName", ""),
float(item.get("originalPrice", 0)),
float(item.get("discountPrice", 0)),
item.get("imgUrl", ""),
item.get("bigImgUrl", ""),
item.get("description", "")[:1000] # 限制描述长度,防止数据库字段溢出
)
cleaned_courses.append(course_value)
return cleaned_courses
5. 数据库存储功能
改进的数据库存储函数,支持数据去重:
def save_to_mysql(course_list, db_manager):
"""将课程数据保存到MySQL数据库,自动去重"""
if not course_list:
logging.warning("没有课程数据需要保存")
return
cleaned_courses = clean_course_data(course_list)
if not cleaned_courses:
logging.warning("清洗后没有有效的课程数据")
return
# 使用INSERT IGNORE避免重复数据
sql = """
INSERT IGNORE INTO course
(id, course_id, product_name, provider, score, learner_count,
lector_name, original_price, discount_price, img_url, big_img_url, description)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
try:
with db_manager as db:
db.cursor.executemany(sql, cleaned_courses)
logging.info(f"成功插入{db.cursor.rowcount}条课程数据")
except Exception as e:
logging.error(f"数据库插入失败: {str(e)}")
6. 主程序流程
最后,我们将所有功能整合到主程序中:
def main():
"""主程序"""
setup_logging()
logging.info("开始爬取网易云课堂课程数据")
# 初始化数据库管理器
db_manager = MySQLManager()
try:
# 获取第一页数据,同时获取总页数
first_page_data = get_json(1)
if not first_page_data:
logging.error("无法获取第一页数据,程序退出")
return
total_page_count = first_page_data["result"]["query"]["totlePageCount"]
logging.info(f"总共发现{total_page_count}页数据")
# 处理第一页数据
course_list = first_page_data["result"]["list"]
save_to_mysql(course_list, db_manager)
# 爬取剩余页面
for page_index in range(2, total_page_count + 1):
logging.info(f"正在爬取第{page_index}/{total_page_count}页...")
page_data = get_json(page_index)
if page_data:
course_list = page_data["result"]["list"]
save_to_mysql(course_list, db_manager)
# 添加延迟,避免请求过于频繁
time.sleep(1)
logging.info("所有数据爬取完成")
except Exception as e:
logging.error(f"程序执行出错: {str(e)}")
finally:
logging.info("程序执行结束")
if __name__ == "__main__":
main()
功能扩展建议
添加代理支持:对于大规模爬取,可以使用代理IP池避免被封数据导出功能:添加将数据导出为CSV或Excel的功能定时任务:使用APScheduler等库实现定时爬取数据可视化:使用Matplotlib或Pyecharts对爬取的数据进行可视化分析Web界面:使用Flask或Django构建一个简单的Web界面展示数据
总结
通过这个项目,我们学习了如何:
使用requests库发送HTTP请求解析JSON格式的数据使用pymysql操作MySQL数据库添加错误处理和日志记录实现数据清洗和验证
这个爬虫项目不仅可以用于爬取网易云课堂的课程数据,稍作修改也可以用于爬取其他类似网站的数据。希望这篇文章能帮助你入门网络爬虫开发,并在实际项目中应用这些技术。
记得在爬取任何网站数据时,要遵守网站的robots.txt协议,尊重网站的数据使用政策,合理控制爬取频率,避免对目标网站造成过大压力。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...