Python爬虫实战:用Requests+正则搞定马蜂窝景点评论,数据直接存TXT

张开发
2026/4/15 1:53:26 15 分钟阅读

分享文章

Python爬虫实战:用Requests+正则搞定马蜂窝景点评论,数据直接存TXT
Python爬虫实战高效抓取旅游网站评论数据并存储为TXT最近在研究旅游景点的用户评价数据时发现手动收集效率实在太低。作为一个Python爱好者我决定用爬虫技术来解决这个问题。本文将分享一个完整的实战案例教你如何用Requests库和正则表达式从旅游网站抓取评论数据并保存为易于分析的TXT格式。1. 准备工作与环境搭建在开始编写爬虫之前我们需要确保开发环境已经准备就绪。Python 3.x是最佳选择因为它对Unicode的支持更好能有效处理中文网页内容。首先安装必要的库pip install requestsRequests库是Python中最受欢迎的HTTP客户端库相比内置的urllib它提供了更简洁的API和更好的性能。我们还需要Python内置的re模块来处理正则表达式无需额外安装。提示建议使用虚拟环境来管理项目依赖避免不同项目间的库版本冲突。对于初学者来说理解网页结构是爬虫开发的第一步。现代网站通常采用前后端分离的架构数据通过API接口提供这正是我们要利用的。通过浏览器开发者工具F12我们可以轻松找到这些API接口。2. 分析目标网站的数据接口旅游网站通常会为每个景点提供专门的评论页面这些评论数据往往通过AJAX请求动态加载。通过分析网络请求我们发现评论数据实际上是通过一个特定的API接口获取的。典型的评论API接口可能长这样https://api.example.com/comments?poi_id12345page1其中poi_id代表景点IDpage参数控制分页。理解这个接口结构对我们的爬虫开发至关重要。为了模拟浏览器行为我们需要设置合适的请求头。最常见的两个头部字段是User-Agent标识客户端类型避免被识别为爬虫Referer表明请求来源有些网站会检查这个字段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, Referer: https://www.example.com/poi/12345.html }3. 编写核心爬取逻辑现在我们可以开始编写爬虫的核心代码了。首先定义一个函数来获取单页评论数据import requests import re def fetch_comments(poi_id, page1): url fhttps://api.example.com/comments?poi_id{poi_id}page{page} headers { User-Agent: Mozilla/5.0..., Referer: fhttps://www.example.com/poi/{poi_id}.html } try: response requests.get(url, headersheaders) if response.status_code 200: return response.text else: print(f请求失败状态码{response.status_code}) return None except Exception as e: print(f发生异常{str(e)}) return None获取到原始数据后我们需要用正则表达式提取有用信息。评论数据通常包含以下几个关键字段评论日期评分星级评论内容对应的正则表达式可能如下def parse_comments(html): # 提取日期 date_pattern rdate:(.*?) dates re.findall(date_pattern, html) # 提取星级 star_pattern rstar:(\d) stars re.findall(star_pattern, html) # 提取评论内容 content_pattern rcontent:(.*?) contents re.findall(content_pattern, html) return list(zip(dates, stars, contents))4. 数据存储与持久化将爬取到的数据保存到本地文件是爬虫的最后一步。TXT格式简单易用适合后续的数据分析。我们可以定义一个函数来处理数据存储def save_to_txt(data, filename): with open(filename, w, encodingutf-8) as f: for item in data: date, star, content item # 清理数据中的特殊字符和HTML标签 content re.sub(r.*?, , content) content content.replace(\n, ).replace(\r, ) f.write(f{date}\t{star}\t{content}\n)为了提高代码的健壮性我们还需要考虑以下几点异常处理网络请求可能失败数据可能不完整速率限制避免对服务器造成过大压力数据去重防止重复存储相同数据import time def crawl_all_comments(poi_id, max_pages5): all_comments [] for page in range(1, max_pages 1): print(f正在爬取第{page}页...) html fetch_comments(poi_id, page) if html: comments parse_comments(html) if comments: all_comments.extend(comments) time.sleep(1) # 礼貌性延迟 return all_comments5. 完整代码示例与优化建议将上述各部分组合起来我们得到一个完整的爬虫实现import requests import re import time from typing import List, Tuple class CommentCrawler: def __init__(self): self.session requests.Session() self.session.headers.update({ User-Agent: Mozilla/5.0..., Accept-Language: zh-CN,zh;q0.9 }) def fetch_page(self, poi_id: str, page: int) - str: url fhttps://api.example.com/comments?poi_id{poi_id}page{page} referer fhttps://www.example.com/poi/{poi_id}.html try: response self.session.get(url, headers{Referer: referer}) response.raise_for_status() return response.text except requests.RequestException as e: print(f请求第{page}页失败: {str(e)}) return None def parse_comments(self, html: str) - List[Tuple[str, str, str]]: date_pattern rdate:(.*?) star_pattern rstar:(\d) content_pattern rcontent:(.*?) dates re.findall(date_pattern, html) stars re.findall(star_pattern, html) contents re.findall(content_pattern, html) # 简单的数据校验 min_length min(len(dates), len(stars), len(contents)) return list(zip(dates[:min_length], stars[:min_length], contents[:min_length])) def save_comments(self, comments: List[Tuple[str, str, str]], filename: str): with open(filename, w, encodingutf-8) as f: for date, star, content in comments: # 数据清洗 clean_content re.sub(r[\\\/], , content) clean_content re.sub(r.*?, , clean_content) clean_content clean_content.replace(\n, ).strip() f.write(f{date}\t{star}\t{clean_content}\n) def crawl(self, poi_id: str, max_pages: int 5): all_comments [] for page in range(1, max_pages 1): print(f处理第{page}页...) html self.fetch_page(poi_id, page) if html: comments self.parse_comments(html) if comments: all_comments.extend(comments) time.sleep(1.5) # 增加延迟避免被封 return all_comments # 使用示例 if __name__ __main__: crawler CommentCrawler() comments crawler.crawl(7391903) # 替换为实际景点ID crawler.save_comments(comments, travel_comments.txt) print(f成功爬取并保存了{len(comments)}条评论)对于想要进一步优化的开发者可以考虑以下几点使用代理IP防止单一IP被封锁实现断点续爬记录已爬取的页码意外中断后可继续添加日志系统便于调试和监控爬虫运行状态支持多种存储格式如CSV、JSON等适应不同需求6. 常见问题与解决方案在实际开发过程中你可能会遇到以下问题问题1请求返回403 Forbidden错误可能原因缺少必要的请求头IP被暂时封锁需要处理Cookie或Token解决方案headers { User-Agent: ..., Referer: ..., Accept: application/json, X-Requested-With: XMLHttpRequest }问题2数据提取不完整可能原因正则表达式不够精确数据是动态加载的响应格式发生了变化解决方案# 更精确的正则表达式示例 content_pattern rcontent:((?:[^\\]|\\.)*)问题3编码问题导致乱码处理方法# 确保使用正确的编码 response.encoding utf-8 content response.text对于更复杂的反爬机制可能需要考虑使用Selenium模拟浏览器行为解析JavaScript生成的内容处理验证码挑战7. 数据清洗与分析建议获取到原始数据后通常需要进行清洗才能用于分析。常见的清洗操作包括去除HTML标签和特殊字符处理空白和换行符标准化日期格式过滤无效或重复数据这里有一个简单的数据清洗函数示例def clean_comment_text(text): # 移除HTML标签 text re.sub(r[^], , text) # 替换HTML实体 text text.replace(amp;, ).replace(lt;, ).replace(gt;, ) # 标准化空白字符 text .join(text.split()) return text.strip()对于想要进行情感分析的开发者可以考虑使用中文分词工具如jieba处理评论内容构建情感词典或使用预训练模型分析评分与情感倾向的关系import jieba from collections import Counter def analyze_comments(comments): all_words [] for _, _, content in comments: words jieba.cut(content) all_words.extend(words) word_counts Counter(all_words) print(最常见的20个词, word_counts.most_common(20))这个爬虫项目最让我惊喜的是只需要不到100行代码就能完成从数据获取到存储的完整流程。在实际使用中我发现合理设置请求间隔1-2秒能显著降低被封风险而使用Session对象可以保持连接复用提高效率。

更多文章