news 2026/6/3 4:05:41

新手避坑指南:用Requests库爬中国大学MOOC,这几个反爬和编码问题你肯定遇到过

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
新手避坑指南:用Requests库爬中国大学MOOC,这几个反爬和编码问题你肯定遇到过

Python爬虫实战:中国大学MOOC数据采集的五大避坑指南

1. 请求头配置的陷阱与优化策略

许多初学者在爬取中国大学MOOC时,最容易犯的错误就是直接复制浏览器中的请求头信息。这种做法看似简单,实则暗藏风险。请求头中的User-AgentRefererCookie等字段都需要特别注意。

一个典型的完整请求头配置应该包含以下关键元素:

headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", "Accept": "application/json, text/javascript, */*; q=0.01", "Accept-Language": "zh-CN,zh;q=0.9", "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", "X-Requested-With": "XMLHttpRequest", "Origin": "https://www.icourse163.org", "Referer": "https://www.icourse163.org/channel/2001.htm", }

动态Cookie处理是另一个常见痛点。很多同学会直接硬编码Cookie值,这会导致爬虫运行一段时间后失效。正确的做法是:

  1. 先发送一个不带Cookie的请求获取初始Cookie
  2. 在后续请求中使用这个Cookie
  3. 定期检查Cookie有效性并更新
session = requests.Session() initial_response = session.get("https://www.icourse163.org") # 后续请求会自动携带Cookie

提示:中国大学MOOC的CSRF token通常包含在Cookie中,名为NTESSTUDYSI,需要确保每次请求都携带最新的token值。

2. POST请求参数构造的正确姿势

中国大学MOOC的API接口大多采用POST方式,参数构造不当会导致请求失败。常见的错误包括:

  • 参数格式错误(应该是URL编码格式)
  • 缺少必填参数
  • 参数值类型不正确

一个典型的课程搜索请求参数应该这样构造:

params = { "mocCourseQueryVo": json.dumps({ "categoryId": -1, "categoryChannelId": 2001, "orderBy": 0, "stats": 30, "pageIndex": 1, "pageSize": 20, "shouldConcatData": True }) }

分页处理是另一个需要注意的点。很多同学在爬取分页数据时,会忽略总页数的判断,导致无限循环或数据缺失。正确的做法是:

  1. 先获取第一页数据,从中解析总页数
  2. 根据总页数设置循环范围
  3. 添加适当的延迟避免被封禁
first_page = requests.post(url, data=params, headers=headers).json() total_pages = first_page["result"]["query"]["totlePageCount"] for page in range(1, total_pages + 1): params["mocCourseQueryVo"]["pageIndex"] = page response = requests.post(url, data=params, headers=headers) # 处理数据 time.sleep(random.uniform(1, 3)) # 随机延迟

3. 编码问题的深度解析与解决方案

中国大学MOOC返回的数据中经常包含Unicode编码的中文字符,直接打印会显示为\uXXXX的形式。很多初学者对此束手无策,其实解决方法很简单。

JSON数据解码的正确流程:

  1. 获取原始响应文本
  2. 解析为JSON对象
  3. 处理其中的Unicode编码
response = requests.post(url, data=data, headers=headers) raw_data = response.text # 获取原始文本 parsed_data = json.loads(raw_data) # 解析JSON # 处理Unicode编码 def decode_unicode(obj): if isinstance(obj, str): return obj.encode('utf-8').decode('unicode-escape') elif isinstance(obj, dict): return {k: decode_unicode(v) for k, v in obj.items()} elif isinstance(obj, list): return [decode_unicode(item) for item in obj] return obj decoded_data = decode_unicode(parsed_data)

CSV文件写入时的编码问题也需要注意。推荐使用utf-8-sig编码,它可以解决Excel打开CSV文件时的乱码问题:

with open('courses.csv', 'w', newline='', encoding='utf-8-sig') as f: writer = csv.writer(f) writer.writerow(['课程ID', '课程名称', '教师', '学校']) # 写入数据...

4. 异常处理与重试机制

网络爬虫在运行过程中难免会遇到各种异常,良好的异常处理机制可以大大提高爬虫的稳定性。常见的异常包括:

  • 网络连接超时
  • 请求频率过高被封禁
  • 服务器返回错误状态码
  • 数据解析失败

健壮的请求函数应该包含以下要素:

def safe_request(url, data=None, headers=None, max_retries=3): for attempt in range(max_retries): try: response = requests.post( url, data=data, headers=headers, timeout=10 ) response.raise_for_status() # 检查HTTP状态码 return response except requests.exceptions.RequestException as e: print(f"请求失败 (尝试 {attempt + 1}/{max_retries}): {e}") if attempt == max_retries - 1: raise time.sleep(2 ** attempt) # 指数退避 return None

代理IP的使用可以在一定程度上避免IP被封禁,但需要注意:

  1. 选择可靠的代理服务商
  2. 定期检测代理IP的可用性
  3. 设置合理的请求间隔
proxies = { 'http': 'http://your.proxy.ip:port', 'https': 'http://your.proxy.ip:port' } try: response = requests.get(url, proxies=proxies, timeout=10) except requests.exceptions.ProxyError: print("代理连接失败,尝试直连") response = requests.get(url, timeout=10)

5. 数据存储与增量爬取策略

爬取大量数据时,合理的存储策略和增量爬取机制可以节省大量时间和资源。

数据库存储比CSV文件更适合大规模数据。以下是使用SQLite的示例:

import sqlite3 def init_db(): conn = sqlite3.connect('mooc_data.db') c = conn.cursor() c.execute('''CREATE TABLE IF NOT EXISTS courses (id TEXT PRIMARY KEY, name TEXT, teacher TEXT, school TEXT, enroll_count INTEGER, update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP)''') conn.commit() return conn def save_course(conn, course_data): c = conn.cursor() c.execute('''INSERT OR REPLACE INTO courses (id, name, teacher, school, enroll_count) VALUES (?, ?, ?, ?, ?)''', (course_data['id'], course_data['name'], course_data['teacher'], course_data['school'], course_data['enroll_count'])) conn.commit()

增量爬取的关键是记录已爬取的数据ID。可以通过以下方式实现:

  1. 在数据库中记录最后爬取的时间戳或ID
  2. 每次爬取前先查询已存在的数据
  3. 只爬取新增或更新的数据
def get_existing_ids(conn): c = conn.cursor() c.execute("SELECT id FROM courses") return set(row[0] for row in c.fetchall()) existing_ids = get_existing_ids(conn) new_courses = [c for c in all_courses if c['id'] not in existing_ids]

在实际项目中,我还发现添加适当的日志记录非常有助于调试和监控爬虫运行状态:

import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('mooc_spider.log'), logging.StreamHandler() ] ) logging.info(f"开始爬取类别 {category_id}") try: # 爬取代码 except Exception as e: logging.error(f"爬取失败: {e}", exc_info=True)
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 3:56:56

3PEAK思瑞浦 TPA6581-DF0R DFN0.8X0.8-4 运算放大器

特性电源电压:2.7 V ~ 5.5 V偏移电压:1.5 mV(最大值)单位增益带宽:10 MHz压摆率:8 V/μs低功耗:每通道 1.2 mA轨到轨输入和输出低 1/f 噪声:在 1 kHz 频率下为 10 nV/√Hz在电源开启…

作者头像 李华