破解豆瓣Ajax动态加载:Python爬取完整长评论和短评

在互联网数据采集领域,动态加载内容一直是爬虫开发者需要应对的重要挑战。豆瓣作为中国知名的文化内容社区,其评论系统采用了Ajax动态加载技术,传统的简单爬虫难以获取完整数据。本文将深入分析豆瓣的Ajax加载机制,并提供完整的Python解决方案。

1. 豆瓣评论加载机制分析

豆瓣电影页面的评论系统采用了典型的"渐进式加载"设计。初始页面只包含少量评论,当用户滚动到页面底部时,会通过Ajax请求加载更多内容。这种设计不仅提升了页面初始加载速度,也为反爬虫提供了一定保护。

通过浏览器开发者工具分析网络请求,我们可以发现:

  • 短评接口:**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">https://movie.douban.com/subject/{movie_id}/comments?start={start}&limit=20&status=P&sort=new_score</font>**
  • 长评接口:**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">https://movie.douban.com/subject/{movie_id}/reviews?start={start}</font>**

这些接口返回的是结构化数据,相比解析HTML更容易提取信息。

2. 技术选型与环境准备

本项目主要使用以下Python库:

  • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">requests</font>**:发送HTTP请求
  • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">json</font>**:解析返回的JSON数据
  • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">time</font>**:添加请求延迟
  • **<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">pandas</font>**:数据存储和处理(可选)

3. 实现豆瓣评论爬虫

3.1 获取短评数据

短评接口返回的是HTML片段,我们需要从中提取数据:

import requests
from bs4 import BeautifulSoup
import time
import random
import json
import csv# 代理信息配置
proxyHost = "www.16yun.cn"
proxyPort = "5445"
proxyUser = "16QMSOML"
proxyPass = "280651"# 构建代理字典
proxies = {"http": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}","https": f"http://{proxyUser}:{proxyPass}@{proxyHost}:{proxyPort}"
}def get_short_comments(movie_id, max_count=200):"""获取豆瓣电影短评:param movie_id: 豆瓣电影ID:param max_count: 最大获取数量:return: 短评列表"""comments = []start = 0limit = 20# 请求头模拟浏览器行为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': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8','Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2','Accept-Encoding': 'gzip, deflate, br','Connection': 'keep-alive','Upgrade-Insecure-Requests': '1',}while start < max_count:url = f'https://movie.douban.com/subject/{movie_id}/comments'params = {'start': start,'limit': limit,'status': 'P','sort': 'new_score'}try:# 添加代理参数response = requests.get(url, params=params, headers=headers, proxies=proxies,  # 添加代理timeout=10)if response.status_code != 200:print(f"请求失败,状态码:{response.status_code}")breaksoup = BeautifulSoup(response.text, 'html.parser')comment_items = soup.select('.comment-item')if not comment_items:print("未找到评论内容,可能已获取所有评论或遇到反爬虫")breakfor item in comment_items:try:# 提取用户信息user = item.select_one('.comment-info a')['title']# 提取评分rating_class = item.select_one('.comment-info .rating')rating = 0if rating_class:rating = int(rating_class['class'][0][-2])/10# 提取评论时间comment_time = item.select_one('.comment-time')['title']# 提取评论内容content = item.select_one('.comment-content').text.strip()comments.append({'user': user,'rating': rating,'time': comment_time,'content': content})except Exception as e:print(f"解析单条评论时出错:{e}")continueprint(f"已获取 {len(comments)} 条短评")# 随机延迟,避免请求过于频繁time.sleep(random.uniform(1, 2))start += limitexcept requests.exceptions.ProxyError as e:print(f"代理连接错误:{e}")breakexcept requests.exceptions.ConnectTimeout as e:print(f"连接超时:{e}")time.sleep(5)  # 超时后等待更长时间continueexcept requests.exceptions.RequestException as e:print(f"网络请求异常:{e}")time.sleep(3)continueexcept Exception as e:print(f"获取短评时出错:{e}")breakreturn comments# 其他函数也需要添加代理支持
def get_long_comments(movie_id, max_count=100):"""获取豆瓣电影长评(需要添加代理支持)"""# 实现代码与get_short_comments类似,需要添加proxies参数passdef get_full_review(review_url):"""获取完整长评内容(需要添加代理支持)"""# 实现代码需要添加proxies参数pass# 使用示例
if __name__ == "__main__":# 测试代理连接try:test_response = requests.get("http://httpbin.org/ip", proxies=proxies, timeout=10)print("代理连接测试成功")print(f"当前代理IP: {test_response.json()['origin']}")except Exception as e:print(f"代理连接测试失败: {e}")# 获取短评movie_id = "1292052"  # 肖申克的救赎comments = get_short_comments(movie_id, max_count=100)print(f"成功获取 {len(comments)} 条评论")

3.2 获取长评数据

长评接口返回的是JSON格式数据,处理起来更加方便:

def get_long_comments(movie_id, max_count=100):"""获取豆瓣电影长评:param movie_id: 豆瓣电影ID:param max_count: 最大获取数量:return: 长评列表"""reviews = []start = 0headers = {'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/plain, */*','Referer': f'https://movie.douban.com/subject/{movie_id}/','X-Requested-With': 'XMLHttpRequest',}while start < max_count:url = f'https://movie.douban.com/subject/{movie_id}/reviews'params = {'start': start}try:response = requests.get(url, params=params, headers=headers, timeout=10)if response.status_code != 200:print(f"请求失败,状态码:{response.status_code}")break# 豆瓣长评页面返回的是HTML,需要解析soup = BeautifulSoup(response.text, 'html.parser')review_items = soup.select('.review-item')if not review_items:print("未找到长评内容,可能已获取所有长评或遇到反爬虫")breakfor item in review_items:try:# 提取标题title = item.select_one('.title-link').text.strip()# 提取作者author = item.select_one('.name').text.strip()# 提取评分rating_ele = item.select_one('.main-title-rating')rating = 0if rating_ele:rating_class = rating_ele['class'][1]rating = int(rating_class[-2])/10# 提取发布时间pub_time = item.select_one('.main-meta').text.strip()# 提取内容摘要content_short = item.select_one('.short-content').text.strip()# 获取完整长评需要进入详情页review_link = item.select_one('.title-link')['href']full_content = get_full_review(review_link)reviews.append({'title': title,'author': author,'rating': rating,'time': pub_time,'content_short': content_short,'content_full': full_content,'link': review_link})except Exception as e:print(f"解析单条长评时出错:{e}")continueprint(f"已获取 {len(reviews)} 条长评")# 随机延迟time.sleep(random.uniform(1, 3))start += len(review_items)except Exception as e:print(f"获取长评时出错:{e}")breakreturn reviewsdef get_full_review(review_url):"""获取完整长评内容:param review_url: 长评详情页URL:return: 完整内容"""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',}try:response = requests.get(review_url, headers=headers, timeout=10)if response.status_code != 200:return "获取失败"soup = BeautifulSoup(response.text, 'html.parser')# 尝试查找长评内容content = soup.select_one('.review-content')if content:# 移除无关元素for elem in content.select('.spoiler-tip, .hidden'):elem.decompose()return content.text.strip()return "内容解析失败"except Exception as e:print(f"获取完整长评内容时出错:{e}")return "请求失败"

3.3 数据存储功能

def save_to_csv(data, filename):"""将数据保存为CSV文件:param data: 数据列表:param filename: 文件名"""if not data:print("无数据可保存")returnkeys = data[0].keys()with open(filename, 'w', newline='', encoding='utf-8-sig') as f:writer = csv.DictWriter(f, fieldnames=keys)writer.writeheader()writer.writerows(data)print(f"数据已保存至 {filename}")def save_to_json(data, filename):"""将数据保存为JSON文件:param data: 数据列表:param filename: 文件名"""with open(filename, 'w', encoding='utf-8') as f:json.dump(data, f, ensure_ascii=False, indent=4)print(f"数据已保存至 {filename}")

4. 反爬虫策略与伦理考量

4.1 应对反爬虫机制

豆瓣有一套完善的反爬虫系统,我们需要采取以下策略:

  1. 设置合理的请求间隔:使用**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">time.sleep()</font>**随机延迟
  2. 轮换User-Agent:模拟不同浏览器和设备
  3. 使用代理IP:防止IP被封锁
  4. 设置Referer头:模拟从正常页面跳转而来
  5. 限制请求频率:避免短时间内过多请求

4.2 伦理与法律考量

在进行网络爬虫开发时,必须注意:

  1. 遵守robots.txt:尊重网站的爬虫协议
  2. 限制数据用途:仅用于个人学习和研究
  3. 不侵犯用户隐私:不收集、泄露用户个人信息
  4. 不过度占用资源:控制请求频率,不影响网站正常运行
  5. 注明数据来源:在使用数据时注明来自豆瓣

5. 扩展与优化建议

  1. 使用异步请求:采用**<font style="color:rgb(64, 64, 64);background-color:rgb(236, 236, 236);">aiohttp</font>**库提高爬取效率
  2. 添加代理池:应对IP封锁问题
  3. 实现断点续传:保存爬取状态,意外中断后可恢复
  4. 添加数据清洗功能:对获取的内容进行进一步处理
  5. 开发可视化界面:使工具更易用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/2846.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式学习笔记-----抽象责任链模式

抽象责任链体系由 5 个关键组件构成责任链上下文它是责任链的 "大脑"&#xff0c;负责处理器的注册、管理和执行调度&#xff0c;是整个模式的核心调度中心。abstractChainHandlerContainer&#xff1a;一个Map<String, List<AbstractChainHandler>>&…

【大前端】实现一个前端埋点SDK,并封装成NPM包

&#x1f680;来做一个支持 React 的前端埋点 SDK&#xff0c;并把它封装成 npm 包 的形式。整体分 3 部分&#xff1a; 核心 SDK&#xff08;独立的采集 & 上报逻辑&#xff09;React Hook / HOC 支持&#xff08;自动埋点&#xff1a;路由变化、组件渲染、点击事件&…

Leetcode+Java+dpI

509.斐波那契数斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a;F(0) 0&#xff0c;F(1) 1 F(n) F(n - 1) F(n - 2)&#xff0c;其中 n >…

IntelliJ IDEA 新手入门教程-Java、Web、Maven创建(带图解)

&#xff08;以下内容大部分来自上述课程&#xff09; ps&#xff1a;本人用的是2021.3.2 1. 下载 可以参考图文&#xff0c;但破解我没试过&#xff1a;图文参考 中文插件&#xff1a;中文插件 ps&#xff1a;中文插件的文章是博客园的&#xff0c;直接一个idea新手专栏&a…

[React]Antd Upload组件上传多个文件

前言实现需求&#xff1a;上传多个文件。其实就是获取多个文件的绝对路径交给后端接口去处理。Upload组件 首先&#xff0c;这里不能调用Upload的onChange函数&#xff0c;因为上传中、完成、失败都会调用这个函数&#xff0c;在多个文件的情况下会出现多次调用问题。改用befor…

Java-多态

多态是 Java 面向对象三大特性&#xff08;封装、继承、多态&#xff09;中最灵活也最核心的概念之一。它允许我们用统一的方式处理不同的对象&#xff0c;大幅提升代码的扩展性和复用性。本文将结合实际案例&#xff0c;从概念、实现到应用&#xff0c;全面解析 Java 多态的核…

Redis常规指令及跳表

第一部分&#xff1a;Redis 常规指令Redis 是一个键值存储系统&#xff0c;其指令通常以 COMMAND KEY_NAME [ARGUMENTS...] 的形式存在。下面我们按照数据结构和功能来分类。1. 全局/键操作指令这些指令不特定于某一数据类型&#xff0c;适用于所有键。指令描述示例KEYS patter…

指纹云手机×Snapchat Spotlight:动态GPS+陀螺仪仿生方案

——基于时空坐标系重构与生物运动模拟的AR营销突破​​一、Snapchat Spotlight广告的技术困局​设备指纹关联风险​Snapchat通过陀螺仪基线值&#xff08;0.1误差&#xff09;和GPS坐标&#xff08;精度&#xff1c;5米&#xff09;构建设备指纹&#xff0c;相似度&#xff1e…

Java 编辑器与 IDE:开发者手中的利剑与盾牌

&#x1f525;个人主页&#xff1a;艾莉丝努力练剑 ❄专栏传送门&#xff1a;《C语言》、《数据结构与算法》、C语言刷题12天IO强训、LeetCode代码强化刷题、洛谷刷题、C/C基础知识知识强化补充、C/C干货分享&学习过程记录 &#x1f349;学习方向&#xff1a;C/C方向学习者…

不再让Windows更新!Edge游戏助手卸载及关闭自动更新

文章目录Windows系统更新问题方法一&#xff1a;通过注册表手动设置1. 打开注册表编辑器2. 定位到目标路径3. 创建新的DWORD值4. 修改数值方法二&#xff1a;命令行设置1. 打开命令提示符2. 输入命令验证设置是否生效恢复更新Edge关闭游戏助手Edge关闭后台运行Edge关闭自动更新…

从Android到鸿蒙:一场本应无缝的转型-优雅草卓伊凡

从Android到鸿蒙&#xff1a;一场本应无缝的转型-优雅草卓伊凡看到Android开发者询问如何转向鸿蒙&#xff0c;卓伊凡不禁摇头&#xff1a;真正的Android工程师根本不需要“学习”鸿蒙&#xff0c;只需要简单查阅文档即可。近年来&#xff0c;随着鸿蒙系统的不断发展&#xff0…

Linux的线程概念与控制

目录 1、Linux的线程概念 1.1 什么是线程 1.2 分页式存储管理 1.3 线程的优点 1.4 线程的缺点 3、Linux的线程控制 3.1 POSIX线程库 3.2 线程创建 3.3 线程退出 3.4 线程等待 3.5 线程分离 1、Linux的线程概念 1.1 什么是线程 首先Linux内核不区分"进程"…