ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Selenium模拟浏览器爬取小红书:从环境搭建到反爬避坑实战

Selenium模拟浏览器爬取小红书:从环境搭建到反爬避坑实战 简介这是一份基于Selenium模拟浏览器行为的小红书关键词搜索与笔记爬取项目源码附带完整文档说明特别适合作为Python期末大作业、课程设计或毕业设计的参考。项目实现了关键词搜索、笔记详情与作者维度数据的采集覆盖作者获赞收藏数、粉丝数、笔记数、评论数、正文内容、发布日期、封面图、点赞收藏数、笔记URL与用户URL等十余个关键字段并在文档中逐一说明便于理解字段含义和二次扩展。压缩包共四个文件包括ipynb格式的爬虫主程序、md格式的使用说明以及json、csv两种爬取结果样例能够直观对比不同格式下的字段组织方式整体体积仅52KB轻量易读。目前已有135人学习下载。对希望掌握Selenium模拟操作、熟悉小红书热门笔记数据采集流程的Python开发者来说这是一份高性价比的入门与实战参考。1. 用 Selenium 模拟浏览器爬小红书为什么绕不开这条慢路子小红书的关键词搜索和笔记详情页几乎全部由 JavaScript 动态渲染直接拿 requests 请求 HTML 拿不到笔记列表拿到的也是空壳。Selenium 模拟浏览器行为等于把浏览器整个搬过来JS 照常执行、接口照常请求能看到什么就能爬到什么这是它在这个场景下最核心的价值——把反爬问题从「破解加密参数」降维成「伪装成真人操作」。项目标题里「Selenium 模拟浏览器行为」这几个字本质上就是在选型上替你做了决定不碰逆向、不走协议用真实浏览器跑真实点击和滚动。这个方案最适合三类人一是做竞品调研和选题分析的内容运营想按关键词批量收集笔记标题、正文、标签和互动数据二是刚入门爬虫的 Python 开发者想找一个能完整跑通「搜索—翻页—进详情—解析—落地」全流程的练手项目三是需要给团队做小红书数据监测工具的产品或后端工程师。它的代价也很明显——慢单个关键词翻几十页可能要几分钟比接口直爬慢一个数量级。但只要把登录态、滚动节奏和选择器维护好它比任何协议模拟方案都稳因为你不是在跟风控对着干是在模拟一个正常用户。2. 搭建 Selenium 采集环境浏览器参数和登录态是两张入场券2.1 安装三件套selenium、webdriver-manager、Chrome 本体设备上先确认有 Python 3.8 和 Chrome 浏览器。常见做法是用 webdriver-manager 自动匹配驱动版本省去手动下载 chromedriver 对不上版本的麻烦。安装命令如下pip install selenium webdriver-managerwebdriver-manager 的核心作用是自动检测本机 Chrome 的版本号并按版本下载对应的 chromedriver 二进制文件。这个过程只需要执行一次之后驱动文件会缓存在用户目录下不需要每次启动都重新下载。只有 selenium 和 webdriver-manager 还不够浏览器本身要能跑起来。Windows 和 macOS 直接装官方 Chrome 即可Linux 服务器如果是精简环境需要额外安装 chromium这里不多展开。装好之后可以跑一个最简脚本验证驱动能不能正常拉起浏览器from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager options webdriver.ChromeOptions() options.add_argument(--start-maximized) driver webdriver.Chrome(serviceService(ChromeDriverManager().install()), optionsoptions) driver.get(https://www.xiaohongshu.com) print(driver.title) driver.quit()这段代码里ChromeDriverManager().install() 负责定位或下载匹配当前 Chrome 的驱动Service 把驱动路径交给 Selenium 去启动。如果直接跑通并打印出页面的 title说明环境没问题。接下来要做的是给浏览器加反检测参数这是后面能不能稳定采集的分水岭。2.2 ChromeOptions 里的关键参数为什么裸启动必被风控直接裸启动 Chrome 访问小红书大概率会卡在登录墙或者滑块验证上。原因是无头浏览器和默认 WebDriver 模式会暴露自动化特征服务端检测到这些特征直接判为机器。我一般会在启动时传入这一组参数options webdriver.ChromeOptions() options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) options.add_argument(--window-size1400,900) options.add_argument(user-agentMozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36) options.add_argument(--langzh-CN) options.add_argument(--disable-gpu) options.add_argument(--no-sandbox)逐个说明--disable-blink-featuresAutomationControlled用来关掉 Chrome 的自动化控制标记这是反检测的第一道门槛excludeSwitches去掉页面里navigator.webdriver标志否则你在控制台执行window.navigator.webdriver返回的是 true很容易暴露window-size不要省略窗口太小会影响懒加载user-agent 建议用当前 Chrome 版本对应的 UA别用默认的 HeadlessChrome--no-sandbox一般只在 root 用户或容器环境下需要本地开发可加可不加。提示这些参数只是降低被识别概率不是免死金牌。小红书的运行商风控算法会综合评估浏览器指纹、行为轨迹和频率参数只能去除明显的自动化特征真正的安全靠后文要讲的操作节奏。2.3 登录态复用扫码一次Cookie 管一周小红书不登录时搜索会被限制得很厉害翻几页就弹验证码。最省事的办法是手动扫码登录一次把 Cookie 序列化到本地之后每次启动直接加载不需要重复扫码。核心逻辑分两段先登录并存 Cookie再加载 Cookie 进首页。存 Cookie 的代码import json import time from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from webdriver_manager.chrome import ChromeDriverManager options webdriver.ChromeOptions() options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) driver webdriver.Chrome(serviceService(ChromeDriverManager().install()), optionsoptions) driver.get(https://www.xiaohongshu.com) time.sleep(30) # 这 30 秒是留给你手动扫码的窗口 with open(xhs_cookies.json, w, encodingutf-8) as f: json.dump(driver.get_cookies(), f, ensure_asciiFalse, indent2) print(cookie saved:, len(driver.get_cookies())) driver.quit()30 秒的 time.sleep 不能写成死等更好的做法是循环轮询检测当前 URL 是否从登录页跳转到了首页或者检测页面里是否出现用户头像元素。不过对于教学项目sleep 足够直观。保存下来的 cookies 会在后续每次启动时被重新注入。加载 Cookie 的代码import json from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager options webdriver.ChromeOptions() options.add_argument(--disable-blink-featuresAutomationControlled) options.add_experimental_option(excludeSwitches, [enable-automation]) driver webdriver.Chrome(serviceService(ChromeDriverManager().install()), optionsoptions) driver.get(https://www.xiaohongshu.com) # 先打开主域确保 Cookie 作用域正确 with open(xhs_cookies.json, r, encodingutf-8) as f: cookies json.load(f) for cookie in cookies: driver.add_cookie(cookie) driver.refresh() time.sleep(3) print(当前页面, driver.current_url)这里有一个关键顺序必须先访问一次小红书主页面拿到页面上下文再 add_cookie最后 refresh。如果直接往空页面塞 Cookie浏览器会因缺少域名上下文而抛异常。加载成功后driver 就处于登录态后续所有搜索请求都带身份信息被弹验证码的概率大幅下降。3. 关键词搜索与笔记列表从输入关键词到拿到全部笔记链接3.1 搜索 URL 的构造方式与参数含义小红书网页版的搜索结果是 URL 驱动的不需要模拟输入框打字再回车。直接在地址栏访问下面这个格式即可https://www.xiaohongshu.com/search_result?keyword露营装备type51keyword是搜索词需要做 URL 编码type51表示只搜笔记不搜用户和商品。type1综合、type61搜索用户、type57搜商品实际采集用 51 最干净。用 driver.get 直接跳转到这个 URL比在输入框里 send_keys 再按回车少一步交互也少一个被检测的点。from urllib.parse import quote keyword 露营装备 search_url fhttps://www.xiaohongshu.com/search_result?keyword{quote(keyword)}type51 driver.get(search_url)用 quote 对中文关键词做编码的目的是避免 URL 里出现非 ASCII 字符有些代理环境和服务端会因此拒绝请求。这个 URL 是列表爬取的总入口后续所有翻页行为都以这个页面为基础。3.2 等待策略显式等待是爬虫的救命稻草页面跳转到搜索结果后笔记列表是异步渲染的不是立刻出现在 DOM 里。这时候如果直接找笔记卡片元素大概率抛 NoSuchElementException。两种常见的等待方式区别很大隐式等待 poll_frequency 固定、影响全局但遇到特定元素加载慢或不存在时会浪费固定的等待时间。显式等待可以精确等到「笔记卡片出现」这个条件成立才继续执行效率高且可控。项目里我全部用显式等待不碰隐式等待。from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By wait WebDriverWait(driver, 15) cards wait.until( EC.presence_of_all_elements_located((By.CSS_SELECTOR, section.note-item)) ) print(本页笔记卡片数, len(cards))这里的section.note-item是搜索结果页里笔记卡片的外层容器小红书改版后一直沿用这个类名。如果这个选择器在你的浏览器里失效——小红书改版频繁是出了名的——可以在页面里右键检查找到包含笔记标题和链接的区块元素把选择器替换成新的。写成独立变量存起来后续维护只改一处。3.3 滚动加载翻页模拟人手滚动不点「下一页」小红书搜索结果页不像传统 PC 端网页有点击翻页按钮而是无限滚动加载。滚到底部会自动加载下一批笔记直到没有更多内容。模拟滚动需要循环执行一段固定脚本import time import random def scroll_to_bottom(driver): 模拟真人滚动分步向下滚动直到出现没有更多提示。 scroll_pause_time random.uniform(1.5, 2.5) current_scroll 0 step random.randint(400, 700) while True: driver.execute_script(fwindow.scrollBy(0, {step});) current_scroll step time.sleep(scroll_pause_time) page_text driver.find_element(By.TAG_NAME, body).text if 没有更多 in page_text or 已到达底部 in page_text: print(滚动结束已翻完所有笔记) break if current_scroll 80000: # 安全上限防止死循环 break time.sleep(2)几个参数的设计逻辑滚动步长 400 到 700 像素是随机取的模拟真实鼠标滚轮或触控板滚动的变速感每次滚动后停顿 1.5 到 2.5 秒给浏览器留出发送请求和渲染新内容的时间不要滚完立刻找元素。安全上限 80000 像素对应大概 100 多篇笔记的量级超过这个值直接跳出防止页面异常时 while 循环卡死。滚动结束后页面上所有已加载的笔记卡片都在 DOM 里这时统一提取链接最稳妥cards driver.find_elements(By.CSS_SELECTOR, section.note-item) note_links [] for card in cards: a_tag card.find_element(By.CSS_SELECTOR, a[href*/explore/]) href a_tag.get_attribute(href) note_links.append(href) print(共获取笔记链接, len(note_links))注意 href 里包含的是/explore/路径这不是笔记详情页的直接地址。正常情况下点击这个链接会跳转到详情页Selenium 采集时直接 driver.get 这个链接也能打开但页面内部会做一层跳转。更好的做法是先拿到这些链接等进入详情页后再逐步解析。4. 笔记详情解析与结构化落地把渲染后的页面变成可用的数据4.1 详情页信息提取标题、正文、点赞、收藏、标签拿到笔记链接列表之后逐条打开并提取信息。详情页的结构相对固定但内容区域是动态渲染的需要等核心 DOM 出现再操作。解析时优先提取以下字段笔记标题、正文文本、发布时间、点赞数、收藏数、评论数、标签列表、图片地址。driver.get(note_url) time.sleep(random.uniform(2, 4)) title_el driver.find_elements(By.CSS_SELECTOR, #detail-title) desc_el driver.find_elements(By.CSS_SELECTOR, #detail-desc, .desc) like_el driver.find_elements(By.CSS_SELECTOR, .like-wrapper .count) fav_el driver.find_elements(By.CSS_SELECTOR, .collect-wrapper .count) title title_el[0].text.strip() if title_el else desc desc_el[0].text.strip() if desc_el else like_count like_el[0].text.strip() if like_el else 0 fav_count fav_el[0].text.strip() if fav_el else 0#detail-title是详情页标题的 ID#detail-desc是正文容器的 ID这两个 ID 在小红书 PC 端页面结构里非常稳定属于详情页的标志性元素。.like-wrapper .count和.collect-wrapper .count是互动数据区如果页面改版导致选择器失效可以在页面内搜索「点赞」「收藏」这些文字找到包裹元素再往上层找 class。用 find_elements 而不是 find_element 的好处是元素缺失时不会立刻抛异常而是返回空列表配合 if 判断让程序继续跑。小红书的互动数据有时会出现「10万」这种格式化文本落到数据表后统一处理成数字。4.2 图片提取与格式转换webp 转 jpg 的真实原因小红书图片地址默认带格式转换参数直接拿到的 src 是.webp格式。本地存储或后续做数据分析时webp 兼容性不如 jpg而且小红书图片 CDN 支持裸地址回源把 URL 里的格式后缀改掉就能拿到原始 jpg。import re import requests def parse_image_urls(driver): imgs driver.find_elements(By.CSS_SELECTOR, div.swiper-slide img, div.img-container img) urls [] for img in imgs: src img.get_attribute(src) if src and src.startswith(http): clean_url re.sub(r!.*$, , src) clean_url clean_url.replace(.webp, .jpg) urls.append(clean_url) return urls正则里去掉!及其后面的尾参这条逻辑处理的是小红书图片 CDN 上类似xxx.jpg!webp的带参 URL。去掉参数后能拿到原始图片再把.webp后缀换成.jpg就是浏览器能直接渲染的常规格式。图片是否需要真下载取决于项目需求。如果只是做文本分析保留 URL 就够了如果要存档再走 requests 流式写入本地。4.3 数据落地按笔记维度写 Markdown 文件和索引表项目要求「笔记爬取」落地格式我建议两件套每篇笔记生成一个独立 Markdown 文件保留完整正文和图片引用同时维护一份全局索引 CSV记录笔记 ID、标题、链接、点赞数、收藏数、抓取时间方便后面对数据做筛选排序。import csv import os from datetime import datetime def save_note(note_id, title, desc, tags, img_urls, note_url): # 保存 Markdown 正文 md_path fnotes/{note_id}.md os.makedirs(notes, exist_okTrue) with open(md_path, w, encodingutf-8) as f: f.write(f# {title}\n\n) f.write(f链接{note_url}\n\n) f.write(f标签{, .join(tags)}\n\n) f.write(desc \n\n) for i, url in enumerate(img_urls): f.write(f![image_{i}]({url})\n\n) # 追加写入全局索引 CSV with open(index.csv, a, encodingutf-8, newline) as f: writer csv.writer(f) writer.writerow([note_id, title, note_url, like_count, fav_count, datetime.now().isoformat()])newline 是 Windows 下防止 CSV 写出时出现空白行的常见坑。note_id 可以从 URL 里提取小红书笔记 URL 通常是/explore/{id}或/discovery/item/{id}格式。如果解析失败直接用 md5 对 URL 取哈希作为笔记 ID保证唯一。5. 小红书反爬与 Selenium 常见踩坑排查5.1 现象启动后浏览器窗口一闪而过就退出驱动刚启动窗口闪现一下立刻关闭没有任何报错输出。最常见的原因是 chromedriver 与 Chrome 主版本不匹配。webdriver-manager 只在网络正常且能访问驱动下载源时才能拉对版本某些内网环境它拿不到匹配驱动就会启动失败。解决先确认本机 Chrome 版本再手动指定驱动版本安装。Chrome 地址栏输入 chrome://version 查看版本号然后到对应的驱动镜像源下匹配版本。如果 webdriver-manager 缓存了旧驱动删掉用户目录下.wdm文件夹再重试强制重新拉取。5.2 现象搜索页能打开但笔记卡片列表始终为空URL 没问题、页面能加载、滚动也执行了但查 DOM 节点就是 0 个。先排除选择器失效——很多小红书页面改版后 class 名换掉。但更隐蔽的问题是滚动脚本执行太快JS 渲染还没完全挂载presence_of_all_elements_located只查了当时已存在的节点后续加载出来的卡片没被捕捉。解决把滚动和等待循环拆分滚动一次、等待一次、提取一次不要在滚动结束后才统一提取。如果页面字面上已经显示笔记数量但 DOM 里查不到说明页面结构可能进入了「防爬假页面」某些反爬策略会给可疑客户端返回一个空壳页面此时检查当前页面截图和driver.page_source长度确认是不是被重定向到了验证页。5.3 现象翻页翻到一半突然弹出登录墙或滑块验证前面几十篇笔记都正常突然出现滑块或者扫码登录提示。这不是 IP 被封也不是账号异常而是行为频率太高风控算法判定操作速度不像真人。短视频平台的内容页加载一般有 500 毫秒到 2 秒的间隔真实用户翻页不会每 1 秒就滚一次屏。解决把固定 sleep 全部改成随机区间延时滚动步长也随机化。我一般用random.uniform(2, 5)作为页面切换的等待时间滚动步长在 400 到 800 像素之间随机。另外每次搜索词采集完强制休息 20 到 40 秒再做下一组搜索。5.4 现象连续采集 30 条笔记后被提示「操作过于频繁」即使延时做得足够随机也会出现操作频率限制的报错页面提示「操作过于频繁请稍后再试」。这种情况通常是账号维度的采集频控跟浏览器指纹关系不大。解决先停 10 分钟以上再继续短时间重试没有意义。更根本的做法是多账号轮询——给每个账号单独建一个 user-data-dir 目录启动时通过options.add_argument(--user-data-dir./profile_01)给每个 Chrome 进程分配独立缓存目录多个账号轮流切换登录状态。注意这是工作量较大的运维操作个人学习项目不必加但做正式数据服务这是一个绕不开的架构选择。5.5 现象详情页图片地址解析出来 404 或打不开解析出来的图片地址在浏览器里直接打开报 404。原因通常是图片 URL 里的签名参数在跳转后被剥离或者用 requests 请求时没有带 Referer 头小红书 CDN 会拒绝无来源请求。解决requests 下载图片时补上Referer: https://www.xiaohongshu.com请求头同时带上笔记页 URL 的 Origin。图片链接的完整度问题则需要在解析时判断 URL 是否以http开头并用正则清理尾参两处同时做才能保证图片下载稳定。6. 并发提速与数据校验从单线程脚本到可用的采集工具单线程逐条打开详情页确实慢但小红书风控对并发也格外敏感。常见的提速做法是 multiprocessing 多进程每个进程单独启动一个 Chrome 实例用不同的--user-data-dir进程间互不共享 Cookie 和浏览器缓存。核心代码骨架from multiprocessing import Pool def worker(keyword_group): # 每个 worker 独立启动 driver、加载独立 profile driver create_driver_with_profile(fprofile_{keyword_group[0]}) for keyword in keyword_group[1]: collect_keyword(driver, keyword) driver.quit() return len(keyword_group[1]) if __name__ __main__: groups [(keyword_group_1, [露营装备, 折叠桌, 营地灯]), (keyword_group_2, [钓鱼椅, 天幕, 气垫床])] with Pool(2) as pool: results pool.map(worker, groups) print(各组完成关键词数:, results)并发数控制在 2 个进程以内开太多会导致 CPU 和内存迅速占用还会被风控精准打击。每个进程处理一组独立关键词组间互不干扰这样即使某一组被限流其他组的数据照常产出。采集完成后的校验环节别省。检查每次保存的 index.csv 和 notes 目录里 md 文件数量是否一致随机抽 3 到 5 篇笔记打开 md 文件核对标题是否为空、正文是否完整、图片引用是否可达。小红书页面改版后选择器失效是最容易「静默失败」的环节——程序跑完没报错、但数据一堆空值这种翻车体验我经历过不止一次。我的习惯是每次正式采集前用小批量测试跑 5 篇人工核对字段无误后再放开全量跑。最后一条建议来自我自己的血泪经验这个项目写到能稳定采集留给自己的使用边界要想清楚。小红书平台对自动化采集有明确限制个人学习、学术研究和小范围数据分析是主流用法产出数据不要用于商业售卖或公开发布采集频率也要克制到不对平台造成压力。这套代码的架构和排查思路换个合规数据源同样能复用——Selenium 模拟浏览器的能力和审美才是项目源码之外真正值钱的东西。希望帮到你。本文还有配套的精品资源点击获取
返回列表