)
一、整体分析这段代码的目标是启动或连接一个 Chrome 浏览器打开小红书探索页让你手动登录搜索关键词“重庆探店”不断下拉页面收集搜索结果里的笔记链接进入每条笔记详情页提取正文、标签、点赞、收藏、时间/IP、图片链接保存到 Excel 文件一句话概括这是一个“半自动登录 自动搜索 自动翻页 自动提取 Excel导出”的浏览器爬虫脚本。二、代码依赖的库代码开头from DrissionPage import Chromium, ChromiumOptionsimport pandas as pdimport timeimport subprocessimport os这里涉及几个常用库DrissionPage这是核心库用来操作浏览器。你可以把它理解成“比 Selenium 更方便一些的浏览器自动化工具”。这里用了两个对象ChromiumOptions设置浏览器参数Chromium创建/连接浏览器实例pandas用于数据处理和导出 Excel。subprocess用于启动外部程序这里就是启动 Chrome 浏览器。os用于拼接路径、读取环境变量比如用户目录。三、配置区的作用KEYWORD 重庆探店MAX_NOTE_COUNT 40OUTPUT_FILE f小红书_{KEYWORD}.xlsxCHROME_PATH rC:\Program Files\Google\Chrome\Application\chrome.exe 要用自己电脑上的浏览器位置DEBUG_PORT 9222这是“参数配置区”。KEYWORD搜索关键词。MAX_NOTE_COUNT最多采集多少条笔记链接。OUTPUT_FILE输出文件名使用 f-string 格式化小红书_重庆探店.xlsxCHROME_PATH本机 Chrome 浏览器路径。DEBUG_PORTChrome远程调试端口。四、什么是 Chrome 远程调试端口这里用了一个技巧不是让脚本直接“新开一个完全受控的浏览器”而是通过 Chrome 的 remote debugging 模式连接浏览器。启动参数里有--remote-debugging-port9222意思是让 Chrome 在 9222 端口开放一个调试接口Python 脚本就能接管这个浏览器。这样做有几个好处可以保留登录状态可以手动登录后再继续脚本比某些自动化方式更稳定一点便于调试五、get_browser() 函数分析def get_browser():try:co ChromiumOptions()co.set_browser_path(CHROME_PATH)co.set_local_port(DEBUG_PORT)page Chromium(co).latest_tabprint(已连接Chrome)return pageexcept:print(启动Chrome...)subprocess.Popen([CHROME_PATH,f--remote-debugging-port{DEBUG_PORT},--no-first-run,--no-default-browser-check,--user-data-dir os.path.join(os.environ.get(LOCALAPPDATA,os.path.expanduser(~)),ChromeDebugProfile)])time.sleep(5)co ChromiumOptions()co.set_browser_path(CHROME_PATH)co.set_local_port(DEBUG_PORT)page Chromium(co).latest_tabreturn page这个函数的作用是“先尝试连接已开启的调试浏览器如果连接失败就启动一个新的 Chrome 再连接”。try 部分尝试连接现有浏览器co ChromiumOptions() 创建浏览器配置对象co.set_browser_path(CHROME_PATH) 指定 Chrome 程序路径co.set_local_port(DEBUG_PORT) 指定调试端口page Chromium(co).latest_tab 连接浏览器并获取最新标签页latest_tab 表示最近的标签页对象。except 部分如果浏览器没开就自动启动subprocess.Popen([...])Popen 是“启动一个进程但不阻塞当前程序”。启动命令里包括CHROME_PATH Chrome 的路径--remote-debugging-port9222 启用调试端口--no-first-run 不要首次运行提示--no-default-browser-check 不要默认浏览器提示--user-data-dir... 指定独立用户数据目录 最终拼接出完整配置目录C:\Users\用户名\AppData\Local\ChromeDebugProfile启动 Chrome 时强制让浏览器使用独立全新的用户配置文件夹避免和你日常 Chrome 冲突可以保留独立登录状态time.sleep(5)给浏览器 5 秒启动时间再去连接。六、get_note_data(page) 函数分析这是“详情页解析函数”。作用给你一个笔记详情页返回一个字典里面是这篇笔记提取出来的数据。结构item {正文: ,标签: ,点赞: ,收藏: ,发表时间、ip: ,图片: }# 正文 try: desc page.ele(xpath://div[contains(class,note-content)]) if desc: item[正文] desc.text except: pass使用XPath 表达式在当前网页中查找第一个class 属性里包含note-content的 div 标签用来定位小红书笔记正文内容所在元素最后把元素对象赋值给desc变量。七、主程序流程分析获取浏览器对象page get_browser()打开小红书探索页page.get(小红书 - 你的生活兴趣社区)提示用户登录print(请先登录小红书)input(登录完成后按回车继续...)这是“人工介入”的步骤。原因登录、验证码、风控通常不适合纯自动处理手动登录反而更稳定。八、搜索部分分析search_input page.ele(#search-input-in-feeds,timeout10)这里使用的是 CSS 选择器含义查找 id 为 search-input-in-feeds 的元素。timeout10表示最多等 10 秒。如果没找到raise Exception(未找到搜索框 search-input-in-feeds)这是主动抛出异常避免后面继续报更模糊的错。然后search_input.click() 把鼠标光标放到搜索框为了输入文字KEYWORDtime.sleep(1)try: search_input.clear() except: pass search_input.input(KEYWORD) time.sleep(1) # 回车搜索 search_input.input(\n) time.sleep(5)知识点clear()清空输入框内容input(KEYWORD)输入文本input(\n)模拟按回车。九.获取每个笔记的url和内容note_links set() #set 集合特点自动去重、元素不重复 while len(note_links) MAX_NOTE_COUNT: cards page.eles(css:.note-item a.cover.mask.ld) for card in cards: try: href card.attr(href) #获取标签里href属性值 if not href: continue if href.startswith(/): #判断是否为相对路径是否以/开头 href (https://www.xiaohongshu.com href) note_links.add(href) except: pass print(f已获取链接{len(note_links)}) # 多次滚动到底部触发懒加载 for _ in range(3): page.scroll.to_bottom() time.sleep(1) time.sleep(2) if len(note_links) MAX_NOTE_COUNT: break print(f共获取 {len(note_links)} 条笔记) # # 采集详情 # results [] for idx, url in enumerate(note_links): #idx 是set集合的索引0123..... try: print(f[{idx1}/{len(note_links)}]) page.get(url) time.sleep(3) item get_note_data(page) #拿到的一条数据的集合 item[链接] url results.append(item) except Exception as e: print(失败, e)十. 完整代码# -*- coding: utf-8 -*- from DrissionPage import Chromium, ChromiumOptions import pandas as pd import time import subprocess import os # # 配置 # KEYWORD 重庆探店 MAX_NOTE_COUNT 40 OUTPUT_FILE f小红书_{KEYWORD}.xlsx CHROME_PATH rC:\Program Files\Google\Chrome\Application\chrome.exe DEBUG_PORT 9222 # # 启动浏览器 # def get_browser(): try: co ChromiumOptions() co.set_browser_path(CHROME_PATH) co.set_local_port(DEBUG_PORT) page Chromium(co).latest_tab print(已连接Chrome) return page except: print(启动Chrome...) subprocess.Popen([ CHROME_PATH, f--remote-debugging-port{DEBUG_PORT}, --no-first-run, --no-default-browser-check, --user-data-dir os.path.join(os.environ.get(LOCALAPPDATA,os.path.expanduser(~)),ChromeDebugProfile) ]) time.sleep(5) co ChromiumOptions() co.set_browser_path(CHROME_PATH) co.set_local_port(DEBUG_PORT) page Chromium(co).latest_tab return page # # 解析详情页 # def get_note_data(page): item { 正文: , 标签: , 点赞: , 收藏: , 发表时间、ip: , 图片: } # 正文 try: desc page.ele(xpath://div[contains(class,note-content)]) if desc: item[正文] desc.text except: pass # 标签 try: tags [] tag_eles page.eles(xpath://a[contains(class,tag)]) for t in tag_eles: txt t.text.strip() if txt: tags.append(txt) item[标签] ,.join(tags) except: pass # 点赞 try: like page.ele(xpath://span[contains(class,like-wrapper)]//span[contains(class,count)]) if like: item[点赞] like.text except: pass # 收藏 try: collect page.ele(xpath://span[contains(class,collect-wrapper)]//span[contains(class,count)]) if collect: item[收藏] collect.text except: pass # 时间 try: date_el page.ele(xpath://span[contains(class,date)]) if date_el: item[发表时间、ip] date_el.text except: pass # 图片 try: imgs [] img_eles page.eles(tag:img) for img in img_eles: src img.attr(src) if not src: continue if sns-webpic in src: if src not in imgs: imgs.append(src) item[图片] |.join(imgs) except: pass return item # # 主程序 # page get_browser() page.get(https://www.xiaohongshu.com/explore) print(请先登录小红书) input(登录完成后按回车继续...) # # 搜索 # print(f开始搜索{KEYWORD}) search_input page.ele(#search-input-in-feeds,timeout10) if not search_input: raise Exception(未找到搜索框 search-input-in-feeds) search_input.click() time.sleep(1) try: search_input.clear() except: pass search_input.input(KEYWORD) time.sleep(1) # 回车搜索 search_input.input(\n) time.sleep(5) # # 获取笔记链接 # note_links set() while len(note_links) MAX_NOTE_COUNT: cards page.eles(css:.note-item a.cover.mask.ld) for card in cards: try: href card.attr(href) if not href: continue if href.startswith(/): href (https://www.xiaohongshu.com href) note_links.add(href) except: pass print(f已获取链接{len(note_links)}) # 多次滚动到底部触发懒加载 for _ in range(3): page.scroll.to_bottom() time.sleep(1) time.sleep(2) if len(note_links) MAX_NOTE_COUNT: break print(f共获取 {len(note_links)} 条笔记) # # 采集详情 # results [] for idx, url in enumerate(note_links): try: print(f[{idx1}/{len(note_links)}]) page.get(url) time.sleep(3) item get_note_data(page) item[链接] url results.append(item) except Exception as e: print(失败, e) # # 保存 # df pd.DataFrame(results) df.to_excel(OUTPUT_FILE,indexFalse) print() print( * 60) print(采集完成) print(总数据, len(df)) print(文件, OUTPUT_FILE) print( * 60)