Python网络爬虫实战:Requests与BeautifulSoup批量采集快手公开数据

📅 2026/7/29 10:36:34
Python网络爬虫实战:Requests与BeautifulSoup批量采集快手公开数据
1. 项目概述为什么我们需要批量采集快手内容做内容运营、市场分析或者单纯想研究某个领域在快手上的生态手动一个个去翻视频、记数据效率低到让人抓狂。我见过不少团队初期靠人力盯屏不仅耗时耗力还容易遗漏关键信息。这时候用 Python 写个脚本进行批量采集就成了一个非常实际的需求。这不仅仅是“偷懒”更是将我们从重复劳动中解放出来把精力聚焦在更有价值的分析和决策上。“Python快手批量采集”这个标题听起来像是一个单纯的技术实现但背后涉及的需求场景其实非常广泛。比如你想追踪某个热门话题下所有爆款视频的文案和互动数据来分析爆款规律或者你需要监控竞品账号的更新频率和内容方向再或者你是研究者需要批量获取某一类视频的元数据如描述、点赞、评论数来做定量分析。手动操作在这些场景下几乎是不可能的任务。这个系列的第一篇我会从最核心、最基础的部分讲起如何安全、稳定地获取到快手网页端的数据。我会带你绕开那些新手最容易踩的坑比如反爬机制、数据解析错误以及如何构建一个可持续运行的采集框架。你会发现用 Python 和几个常见的库就能搭建起一个高效的“信息雷达”。无论你是 Python 新手还是有一定基础想挑战网络爬虫的开发者跟着这篇实操指南都能一步步实现自己的采集目标。2. 核心思路与工具选型为什么是 Requests BeautifulSoup面对一个像快手这样的大型平台采集策略的制定直接决定了项目的成败。我的核心思路是模拟普通用户浏览网页的行为从公开的网页页面中提取结构化数据。这意味着我们不会、也不应该去触碰任何需要登录或个人隐私数据的接口更不会尝试破解客户端协议一切操作都在公开、合法的网页访问范畴内。为什么首选网页端而不是 App 端原因很简单门槛低易于分析和调试。快手 App 的数据通信往往经过加密和压缩逆向分析难度极大而网页端m.kuaishou.com 或 www.kuaishou.com的 HTML 结构相对清晰通过浏览器开发者工具就能直观地看到数据是如何加载和渲染的。这对于我们理解和编写采集脚本至关重要。在工具选型上我经过多次对比和实战最终确定了这个经典组合Requests: 这是 Python 社区进行 HTTP 请求的“事实标准”。它简单、优雅功能却非常强大。我们需要用它来向快手的服务器发送请求模拟打开网页的动作并把服务器返回的 HTML 代码“拿回来”。BeautifulSoup4 (bs4): 拿到一堆杂乱无章的 HTML 代码后我们需要从中精准地“捞出”我们需要的信息比如视频标题、发布者、点赞数。BeautifulSoup 就是一个超级好用的“数据捞取器”它可以根据标签名、CSS 类名等属性轻松地解析和提取 HTML 中的特定内容。正则表达式 (re): 有些数据特别是动态加载的数据可能直接嵌在 HTML 的 JavaScript 脚本变量里结构不那么规整。这时候BeautifulSoup 可能力有未逮就需要正则表达式这把“手术刀”进行更灵活的文本匹配和提取。为什么不直接用 SeleniumSelenium 能模拟浏览器真实操作对于需要执行 JavaScript 才能加载数据的页面非常有效。但在初期我们的目标是快速、轻量地获取核心数据。Selenium 需要启动一个浏览器实例资源消耗大速度也慢更适合作为高级阶段应对复杂反爬的备选方案。我们第一步要追求的是效率和简洁。注意任何爬虫操作都必须遵守网站的robots.txt协议并尊重版权和个人隐私。我们的示例仅针对公开的、非个人的页面信息进行采集且会严格控制请求频率避免对目标服务器造成压力。批量采集是为了学习和分析切勿用于侵犯他人权益或从事非法活动。2.1 环境准备与依赖安装工欲善其事必先利其器。在开始写代码之前我们需要一个干净的 Python 环境以及必要的库。我强烈建议使用虚拟环境Virtual Environment来管理项目依赖这可以避免不同项目间的库版本冲突。如果你使用的是 macOS 或 Linux打开终端如果是 Windows打开命令提示符CMD或 PowerShell然后执行以下步骤创建项目目录并进入mkdir kuaishou_crawler cd kuaishou_crawler创建并激活虚拟环境使用venv(Python 3.3 内置)# 创建虚拟环境环境文件夹名为 venv python3 -m venv venv # 激活虚拟环境 # macOS/Linux: source venv/bin/activate # Windows: venv\Scripts\activate激活后你的命令行提示符前通常会显示(venv)表示你已经在虚拟环境中了。安装必要的库 在激活的虚拟环境中运行以下命令pip install requests beautifulsoup4 lxmlrequests: 用于发送 HTTP 请求。beautifulsoup4: 用于解析 HTML。lxml: 这是一个解析器比 Python 内置的html.parser速度更快、容错能力更强是 BeautifulSoup 的推荐解析器。安装完成后你可以通过pip list命令确认这三个库已成功安装。3. 快手网页结构分析与数据定位在动手写代码之前我们必须先当一回“侦察兵”。打开浏览器推荐 Chrome 或 Edge访问快手的网页版比如一个热门视频页面。接下来我们要使用浏览器自带的“开发者工具”按 F12 键打开来窥探页面的内部结构。我们的目标是找到数据在 HTML 中的“藏身之处”。以视频信息为例我们关心视频标题它在哪个 HTML 标签里有什么特殊的class或id属性作者信息作者名字和主页链接的标签是什么互动数据点赞数、评论数、转发数这些数字是如何呈现的是纯文本还是嵌套在更深的标签里视频描述描述文本的容器标签是什么实操过程在快手网页上找到一个你想分析的视频页面。按F12打开开发者工具切换到“元素”Elements面板。点击开发者工具左上角的箭头图标或按CtrlShiftC然后用鼠标去点击页面上的视频标题。此时开发者工具会自动定位并高亮显示包含标题的 HTML 代码段。仔细观察这段代码。你可能会看到类似这样的结构h1 classvideo-title这是一个非常有趣的视频标题/h1 div classauthor-info a href/profile/user123 classauthor-name创作者小明/a /div div classstats span classlike-count1.2万/span span classcomment-count3456/span /div记下这些关键的CSS 类名class或标签名。这就是我们后续用 BeautifulSoup 进行提取的“坐标”。一个重要发现你可能很快会注意到快手的大部分页面数据尤其是视频列表并不是直接写在初始的 HTML 中的。当你滚动页面时新的视频才会加载出来。这意味着数据是通过Ajax 请求动态获取的。我们需要在开发者工具的“网络”Network面板中筛选XHR或Fetch请求找到真正返回数据的那个接口。这个接口的 URL 和响应格式通常是 JSON才是我们采集的终极目标。这一步是爬虫从“简单”到“实用”的关键跨越。3.1 处理动态加载内容与接口分析这是批量采集快手的核心技术点。快手网页版采用了前后端分离的架构首屏加载一个基础框架内容数据通过后台接口异步填充。这能提升用户体验但对爬虫来说直接解析初始 HTML 是拿不到完整视频列表的。操作步骤打开开发者工具的“网络”Network面板。清空现有记录然后刷新快手页面比如“发现”页或某个用户主页。开始慢慢向下滚动页面触发新内容的加载。观察“网络”面板中新增的请求。重点关注类型为XHR或Fetch的请求。逐个点击这些请求查看其“预览”Preview或“响应”Response选项卡。你要寻找的是一个返回内容为JSON格式并且里面包含videoList、feeds等字段数据结构清晰包含了视频ID、标题、封面图、用户信息、统计数据的请求。找到这个关键接口后点击它查看其“标头”Headers信息。你需要记录下请求 URL (Request URL): 这是接口地址通常包含一些查询参数query parameters。请求方法 (Request Method): 通常是GET或POST。请求头 (Request Headers): 特别是User-Agent用于标识浏览器身份、Referer页面来源、以及可能的Cookie会话信息初期可暂不处理。举个例子你可能会找到一个类似https://www.kuaishou.com/graphql的请求方法是POST其请求负载Payload里带有复杂的查询语句。或者找到一个类似https://api.kuaishou.com/rest/photo/share/info?photoIdxxx的GET请求。前者需要你模拟构建 GraphQL 查询后者则相对简单。实操心得初期为了降低难度我们可以优先寻找那些结构相对简单的GET接口。有些接口的 URL 参数可能包含page页码、count每页数量这正好方便我们实现批量翻页采集。将找到的接口 URL 和必要的请求头记录下来这是我们下一步编写爬虫脚本的“地图”。4. 编写基础采集脚本从单个页面开始有了前面的侦察结果我们现在可以开始编写第一个爬虫脚本了。我们从最简单的场景开始采集一个已知视频页面的静态信息。假设我们已经通过分析找到了视频标题和作者信息的 HTML 选择器。创建一个名为crawl_single_video.py的文件。4.1 发送请求与处理响应import requests from bs4 import BeautifulSoup import re # 目标视频页面的URL (示例请替换为实际URL) url https://www.kuaishou.com/short-video/某个视频ID # 设置请求头模拟浏览器访问 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } try: # 发送GET请求 response requests.get(url, headersheaders, timeout10) # 检查请求是否成功 (状态码200表示成功) response.raise_for_status() # 设置响应的编码通常为utf-8 response.encoding response.apparent_encoding or utf-8 # 将响应内容解析为BeautifulSoup对象使用lxml解析器 soup BeautifulSoup(response.text, lxml) except requests.exceptions.RequestException as e: print(f请求发生错误: {e}) exit() except Exception as e: print(f解析发生错误: {e}) exit()代码解释headers: 这是告诉服务器“我是一个正常的浏览器”。User-Agent是关键没有它或使用默认的 Python-UA很容易被网站识别为爬虫并拒绝服务。response.raise_for_status(): 如果 HTTP 请求返回了错误状态码如 404、500这一行会抛出异常让我们能及时处理错误而不是继续解析错误页面。timeout10: 设置超时时间防止因网络问题导致脚本长时间挂起。4.2 使用BeautifulSoup提取数据现在假设我们通过之前的分析发现视频标题在一个class为video-title的h1标签里作者名在一个class为author-name的a标签里。# 提取视频标题 # find() 方法找到第一个匹配的标签 title_tag soup.find(h1, class_video-title) # 使用 .get_text(stripTrue) 获取标签内的文本并去除首尾空白字符 video_title title_tag.get_text(stripTrue) if title_tag else 标题未找到 # 提取作者名 author_tag soup.find(a, class_author-name) author_name author_tag.get_text(stripTrue) if author_tag else 作者未找到 author_link author_tag[href] if author_tag and author_tag.has_attr(href) else # # 注意href可能是相对路径可能需要拼接基础URL full_author_link requests.compat.urljoin(url, author_link) print(f视频标题: {video_title}) print(f作者: {author_name}) print(f作者主页: {full_author_link})关于选择器soup.find(tag, class_classname): 查找第一个具有指定标签名和类名的元素。soup.find_all(tag, class_classname): 查找所有具有指定标签名和类名的元素返回一个列表。除了class_还可以用其他属性如id,attrs{data-video-id: 123}等。4.3 处理复杂数据使用正则表达式有时数据藏在script标签的 JavaScript 变量中。例如视频的精确点赞数可能在一个window.__INITIAL_STATE__这样的对象里。这时BeautifulSoup 只能帮我们拿到整个脚本字符串需要用正则表达式re模块来“抠”出我们需要的数据。# 假设点赞数藏在某个script标签的JSON数据中 script_tags soup.find_all(script) for script in script_tags: if script.string and likeCount in script.string: # 使用正则表达式查找 likeCount 后面的数字 match re.search(rlikeCount\s*:\s*(\d), script.string) if match: like_count match.group(1) print(f点赞数: {like_count}) break else: print(未在脚本中找到点赞数信息)注意事项网页结构可能会随时调整。今天有效的 CSS 类名明天可能就变了。因此你的选择器需要有一定的容错性就像上面的if title_tag else 标题未找到并且核心脚本要易于维护和更新。最好的做法是将选择器字符串作为配置项放在代码开头而不是硬编码在提取逻辑里。5. 进阶批量采集与翻页逻辑实现采集单个页面只是开始批量采集才是我们的目标。这涉及到两个核心问题如何生成一批页面的URL以及如何实现自动翻页。5.1 基于列表页或接口的批量采集最理想的批量采集源头是快手的列表页或我们之前找到的 JSON 接口。方案一采集用户主页视频列表如果我们想采集某个特定作者的所有视频可以分析其主页的视频列表加载接口。分析其主页滚动加载的 XHR 请求找到返回视频列表的接口 URL。观察该 URL 的参数常见的有userId用户ID、page页码、size每页数量。编写循环不断递增page参数发送请求直到返回的视频列表为空。import requests import json base_api_url https://api.example-kuaishou.com/user/videos # 示例API需替换为真实接口 user_id target_user_id params { userId: user_id, page: 1, size: 20 } headers { User-Agent: ... # 你的请求头 } all_videos [] page 1 max_pages 50 # 设置一个最大页数防止无限循环 while page max_pages: params[page] page print(f正在采集第 {page} 页...) try: resp requests.get(base_api_url, paramsparams, headersheaders, timeout10) data resp.json() # 假设接口返回JSON video_list data.get(data, {}).get(list, []) if not video_list: print(没有更多视频了采集结束。) break all_videos.extend(video_list) # 可以在这里添加短暂休眠尊重服务器 # time.sleep(1) page 1 except Exception as e: print(f采集第 {page} 页时出错: {e}) break print(f共采集到 {len(all_videos)} 个视频信息。) # 接下来可以遍历 all_videos提取每个视频的详细信息方案二通过关键词搜索采集如果你想采集某个关键词如“Python教程”下的视频可以分析快手搜索页的接口。逻辑类似只是参数变成了keyword关键词和page。5.2 控制请求频率与遵守Robots协议毫无节制地高速请求会被网站视为攻击导致 IP 被封。必须实施请求间隔 throttling 。import time # 在每次循环请求后添加 time.sleep(2) # 休眠2秒这是一个比较保守友好的间隔更优雅的做法是使用随机间隔模拟人类操作import random time.sleep(random.uniform(1, 3)) # 随机休眠1到3秒此外一定要检查目标网站的robots.txt文件例如https://www.kuaishou.com/robots.txt查看哪些路径是允许或禁止爬虫访问的。虽然requests不会自动遵守但作为一个有道德的程序员我们应该主动规避被明确禁止的路径。5.3 数据存储从JSON文件到数据库采集到的数据需要持久化保存。对于初学者和小规模采集JSON 或 CSV 文件是很好的选择。保存为JSON文件import json # 假设 all_video_info 是一个包含多个视频字典的列表 all_video_info [...] # 你的视频数据列表 filename fkuaishou_videos_{user_id}.json with open(filename, w, encodingutf-8) as f: # ensure_asciiFalse 确保中文正常显示indent2 让文件有缩进便于阅读 json.dump(all_video_info, f, ensure_asciiFalse, indent2) print(f数据已保存到 {filename})保存为CSV文件import csv # 定义CSV文件的列头 fieldnames [video_id, title, author, like_count, comment_count, share_count, pub_time] filename fkuaishou_videos_{user_id}.csv with open(filename, w, newline, encodingutf-8-sig) as f: # utf-8-sig 解决Excel打开中文乱码 writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() # 写入列名 for video in all_video_info: # 确保video字典的键与fieldnames匹配不存在的键可以赋予空值 row {field: video.get(field, ) for field in fieldnames} writer.writerow(row) print(f数据已保存到 {filename})当数据量变大时应考虑使用数据库如 SQLite轻量单文件、MySQL 或 MongoDB适合非结构化数据。使用数据库可以方便地进行去重、查询和复杂分析。6. 常见问题、反爬策略与应对技巧在实际操作中你绝不会一帆风顺。下面是我踩过坑后总结的一些常见问题及应对策略。6.1 请求被拒绝或返回异常数据问题收到403 Forbidden、429 Too Many Requests状态码或者返回的 HTML 是验证页面如要求输入验证码。原因你的请求被识别为爬虫。应对完善请求头确保User-Agent是真实的浏览器字符串。可以添加Referer通常设置为目标网站的域名页、Accept-Language等。使用会话Sessionrequests.Session()可以自动管理 Cookies使多次请求看起来更像同一个用户在操作。session requests.Session() session.headers.update(headers) # 设置会话级别的headers response session.get(url)添加延迟这是最重要的措施如前所述在请求间加入随机延迟。代理IP池如果单个IP被封锁就需要使用代理IP。可以购买付费代理服务或者寻找免费的代理IP但免费代理通常不稳定、速度慢。使用代理时需要在请求中设置proxies参数。proxies { http: http://your-proxy-ip:port, https: http://your-proxy-ip:port, } response requests.get(url, headersheaders, proxiesproxies, timeout10)6.2 数据解析失败问题BeautifulSoup 找不到你预期的标签返回None。原因网页结构已变更这是最常见的原因。网站前端更新了。数据是动态加载的你解析的初始 HTML 不包含数据数据由 JavaScript 后续加载。选择器写错了类名或标签名有误。应对重新分析用开发者工具再次确认当前页面的 HTML 结构。转向接口放弃解析 HTML直接寻找并调用动态加载数据的 JSON 接口。这是更稳定、更高效的方法。使用更灵活的选择器不要依赖过于具体的类名可以尝试用标签组合、属性包含选择器等。# 例如寻找包含“点赞”文本的span标签的父级div soup.find(div, textre.compile(点赞)).find_next_sibling(span)备用方案如果接口也找不到或难以模拟可以考虑使用Selenium或Playwright这类浏览器自动化工具它们能真正执行 JavaScript获取渲染后的完整页面。但代价是速度慢、资源消耗大。6.3 数据格式化与清洗问题提取到的数字带有“万”、“亿”等中文单位或者时间格式不统一。处理编写清洗函数。def parse_count(count_str): 将‘1.2万’、‘3456’这样的字符串转换为整数 if not count_str: return 0 count_str count_str.strip() if 万 in count_str: return int(float(count_str.replace(万, )) * 10000) elif 亿 in count_str: return int(float(count_str.replace(亿, )) * 100000000) else: try: return int(count_str.replace(,, )) # 处理千位分隔符 except ValueError: return 0 # 示例 print(parse_count(1.2万)) # 输出 12000 print(parse_count(3,456)) # 输出 34566.4 连接超时与重试机制网络不稳定可能导致单次请求失败。实现一个简单的重试机制能大大提高脚本的健壮性。import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry # 创建带重试策略的Session session requests.Session() retry_strategy Retry( total3, # 最大重试次数 backoff_factor1, # 重试等待时间因子 status_forcelist[429, 500, 502, 503, 504], # 遇到这些状态码才重试 ) adapter HTTPAdapter(max_retriesretry_strategy) session.mount(http://, adapter) session.mount(https://, adapter) try: response session.get(url, headersheaders, timeout5) response.raise_for_status() except requests.exceptions.RequestException as e: print(f请求失败已重试多次: {e})7. 项目结构优化与代码封装当脚本功能变多后一个清晰的目录结构能让项目更易于维护和扩展。我建议这样组织你的“快手批量采集”项目kuaishou_crawler/ ├── config.py # 配置文件存放请求头、API地址、数据库连接等常量 ├── crawler.py # 核心爬虫类封装请求、解析、存储逻辑 ├── utils.py # 工具函数如清洗数据、处理时间的函数 ├── main.py # 主程序入口调度整个采集流程 ├── requirements.txt # 项目依赖列表 ├── data/ # 存放采集的JSON/CSV数据 │ └── 2024-05-20_user_videos.json └── logs/ # 存放运行日志可选 └── crawler.logcrawler.py示例骨架import requests from bs4 import BeautifulSoup import time import random import logging from utils import parse_count, format_date class KuaishouCrawler: def __init__(self, headersNone, delay_range(1, 3)): self.session requests.Session() if headers: self.session.headers.update(headers) self.delay_range delay_range self.logger logging.getLogger(__name__) def _request_with_retry(self, url, max_retries3): 带重试的请求方法 for i in range(max_retries): try: resp self.session.get(url, timeout10) resp.raise_for_status() self._random_delay() return resp except Exception as e: self.logger.warning(f请求 {url} 失败第{i1}次重试。错误: {e}) time.sleep(2 * (i 1)) # 重试等待时间递增 self.logger.error(f请求 {url} 失败已达最大重试次数。) return None def _random_delay(self): 随机延迟模拟人工操作 delay random.uniform(*self.delay_range) time.sleep(delay) def fetch_user_videos(self, user_id, max_pages10): 采集指定用户视频列表 videos [] # ... 具体的采集逻辑调用 _request_with_retry return videos def parse_video_page(self, html_content): 解析单个视频页面HTML soup BeautifulSoup(html_content, lxml) # ... 具体的解析逻辑调用 utils 中的函数 video_info {} return video_info # 在 main.py 中这样使用 if __name__ __main__: from config import DEFAULT_HEADERS crawler KuaishouCrawler(headersDEFAULT_HEADERS) results crawler.fetch_user_videos(target_user_id, max_pages5) print(f采集完成共 {len(results)} 条记录。)这样的结构将网络请求、数据解析、工具函数分离开符合单一职责原则。config.py集中管理配置修改起来非常方便。main.py作为入口清晰简洁。8. 法律与道德边界负责任地采集最后也是最重要的一部分我们必须严肃讨论法律和道德问题。技术本身是中立的但使用技术的方式决定了其性质。遵守robots.txt这是网站与爬虫之间的基本协议。明确禁止爬取的目录不要触碰。尊重版权与隐私采集公开信息用于个人学习、研究或合法的数据分析是通常可以接受的。但绝对禁止将采集的内容用于商业牟利而不注明来源或获得授权。采集用户的隐私信息如手机号、私信内容等。对采集的数据进行篡改、诽谤或用于其他非法用途。控制访问频率以不影响网站正常运行为前提添加足够的延迟避免对服务器造成 DDOS 攻击般的压力。查看服务条款在使用快手或任何平台的服务前阅读其用户协议了解其对数据抓取的明确规定。我个人的实践原则是只采集最小必要的数据用于生成聚合性的、非竞争性的、具有公共价值的洞察报告并且始终注明数据来源。例如分析某个垂直领域的内容趋势而不是原封不动地搬运内容。批量采集是一个强大的工具它能帮你打开一扇洞察数据世界的新窗口。希望这篇超过五千字的详细指南能帮你从零开始构建起属于自己的、稳健高效的快手数据采集方案。记住耐心分析、礼貌访问、妥善使用数据是每一个爬虫工程师应该恪守的准则。在接下来的系列文章中我们会探讨更深入的话题比如如何应对更复杂的反爬机制、如何将采集的数据进行可视化分析等。