资讯详情 Python3+Selenium实战:Facebook好友列表抓取与CSV导出
📅 2026/10/13 14:05:02
简介这是一份面向Python爬虫初学者与自动化测试爱好者的Facebook好友列表抓取脚本基于Python3编写借助Selenium驱动Firefox完成自动登录与数据提取用户只需填入邮箱和密码即可运行适合想了解浏览器自动化、登录流程模拟与页面解析的入门者练手。压缩包共6个文件约4.03MB包含1个核心py脚本、2个md说明文档、1个gif演示动图以及license和gitignore等辅助文件脚本主体清晰文档与动图便于快速理解运行效果。资源已积累2889人学习下载热度较高。通过阅读脚本与说明可掌握Selenium与Firefox的配合方式、argparse命令行参数设计、登录超时与登录文件等选项的用法并参考BeautifulSoup4相关解析思路为后续扩展抓取字段、处理登录态与调试排错提供可复用的代码骨架。1. 朋友列表抓取一个被低估的自动化入口做海外社媒数据的朋友多半有过这种经历运营需要一批种子用户做冷启动市场部想分析某个垂直圈层的关系网络或者你自己想搞清楚一个公开主页到底连接了哪些人。手动点开、滚动、复制、粘贴一百个好友能耗掉整个下午而且极易触发平台的风控。selenium_facebook_scraper这个标题指向的正是这类需求的最小可行方案——用 Python3 加 Selenium 驱动真实浏览器把朋友列表这种半结构化页面里的昵称、主页链接、头像地址批量落成 CSV。它不碰私有接口不逆向加密参数靠的是模拟真人操作所以对新手友好对熟手也留足了调参空间。适合谁适合需要快速验证关系网络假设、又不想一上来就啃复杂爬虫框架的从业者。下面我把这套方案的选型逻辑、代码骨架、参数边界和血泪坑位一次讲透。2. 为什么用 Selenium 而不是直接怼接口选型与前置准备2.1 朋友列表页面的加载特性决定了工具选择朋友列表不是一张静态 HTML 表。打开一个公开主页的好友标签你会看到首屏只渲染十几个卡片往下滚动才触发异步加载而且滚动到底部时会出现一个短暂的加载指示器。用requests直接请求拿到的往往是空壳或者只有第一页数据。Selenium 的价值在于它驱动的是真实浏览器内核JavaScript 执行、懒加载触发、动态 DOM 插入都和真人操作一致。常见做法是配合WebDriverWait做显式等待等某个卡片元素出现再继续而不是死等固定秒数。另一个选型理由是登录态朋友列表通常要求账号已登录Selenium 可以复用本地浏览器配置或手动扫码后的会话省去处理复杂 Token 的麻烦。2.2 环境搭建与依赖安装的最小命令集先确认本机有 Python3.8 以上和对应版本的浏览器驱动。我一般用虚拟环境隔离避免污染全局包。下面这套命令在 macOS 和主流 Linux 发行版上都能跑通Windows 把激活命令换成venv\Scripts\activate即可。# 创建并激活虚拟环境 python3 -m venv venv source venv/bin/activate # 安装核心依赖 pip install selenium webdriver-manager pandas # 验证 selenium 版本建议 4.x 以上 python -c import selenium; print(selenium.__version__)webdriver-manager的作用是自动下载并匹配浏览器驱动省去手动对照版本号的痛苦。pandas只用于最后导出 CSV如果你习惯用csv标准库也可以不装。安装完成后新建一个scraper.py后续所有代码都写在这里。2.3 初始化 WebDriver 的关键参数与反检测基线直接webdriver.Chrome()启动的浏览器会带一堆自动化特征平台的风控系统很容易识别。我一般会加几个启动参数并把navigator.webdriver这个属性抹掉。注意这里不涉及任何绕过登录或破解加密的操作只是让浏览器行为更接近普通用户。from selenium import webdriver from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.chrome.options import Options options Options() # 禁用“正受到自动化测试软件控制”的提示条 options.add_experimental_option(excludeSwitches, [enable-automation]) options.add_experimental_option(useAutomationExtension, False) # 设置一个常见的桌面窗口尺寸避免移动端布局干扰 options.add_argument(--window-size1400,900) # 可选指定用户数据目录复用已登录的会话 # options.add_argument(--user-data-dir/path/to/your/chrome/profile) driver webdriver.Chrome(serviceService(ChromeDriverManager().install()), optionsoptions) # 抹掉 webdriver 标记 driver.execute_cdp_cmd(Page.addScriptToEvaluateOnNewDocument, { source: Object.defineProperty(navigator, webdriver, {get: () undefined}) })参数说明excludeSwitches去掉顶部提示条减少视觉干扰window-size固定为桌面尺寸因为朋友列表在移动端布局下 DOM 结构不同选择器会失效user-data-dir是可选的重头戏如果你已经在该浏览器配置里登录过目标账号加上这个参数就能直接带着登录态启动省去每次扫码。注意用户数据目录不要和日常使用的浏览器实例同时打开否则会锁冲突。3. 从滚动加载到 CSV 落盘核心抓取逻辑拆解3.1 定位朋友卡片选择器的稳定性与降级策略朋友列表的 DOM 结构会随平台改版变化所以选择器不能写死。我的习惯是先用浏览器开发者工具找到卡片容器的共同特征比如div[rolemain]下带有特定>from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def find_friend_cards(driver, timeout15): # 候选选择器列表按优先级排列 candidates [ div[rolemain] div[data-visualcompletionignore-dynamic] a[href*/friends], div[rolemain] a[href*friend], div[rolemain] div[rolearticle] a, ] for sel in candidates: try: WebDriverWait(driver, timeout).until( EC.presence_of_element_located((By.CSS_SELECTOR, sel)) ) cards driver.find_elements(By.CSS_SELECTOR, sel) if cards: print(f命中选择器: {sel}, 卡片数: {len(cards)}) return cards except Exception: continue raise RuntimeError(所有候选选择器均未命中请检查页面是否加载或登录态是否失效)逻辑说明WebDriverWait最多等 15 秒让异步内容有机会渲染presence_of_element_located只要求元素出现在 DOM 里不要求可见因为部分卡片可能在视口外。候选选择器从具体到宽泛避免因一次改版就全盘失效。如果全部落空直接抛异常而不是返回空列表方便上层决定是否重试。3.2 滚动加载的节奏控制次数、间隔与终止条件滚动是这套脚本最容易被风控盯上的动作。我的经验是不要用window.scrollTo(0, document.body.scrollHeight)一步到底那样滚动事件触发太密集。改成小步长、随机间隔并且每滚几次就检查一次卡片数量是否还在增长。import time import random def scroll_and_collect(driver, max_scrolls50, pause_range(1.5, 3.5)): last_count 0 stable_rounds 0 for i in range(max_scrolls): # 小步滚动每次滚一屏的 80% 左右 driver.execute_script(window.scrollBy(0, window.innerHeight * 0.8);) # 随机停顿模拟真人阅读节奏 time.sleep(random.uniform(*pause_range)) cards driver.find_elements(By.CSS_SELECTOR, div[rolemain] a[href*friend]) current_count len(cards) print(f第 {i1} 次滚动当前卡片数: {current_count}) if current_count last_count: stable_rounds 1 # 连续 3 次数量不变认为已到底 if stable_rounds 3: print(卡片数连续稳定停止滚动) break else: stable_rounds 0 last_count current_count return cards参数说明max_scrolls是硬上限防止页面无限加载导致脚本卡死pause_range给随机间隔1.5 到 3.5 秒之间比较接近真人浏览速度stable_rounds设为 3 是经验值太少容易误判太多浪费时间。注意每次滚动后重新查询卡片因为 DOM 会动态插入新节点之前拿到的列表引用可能已经过期。3.3 提取字段并导出 CSV去重、编码与列顺序拿到卡片元素后逐个提取昵称、主页链接和头像地址。昵称通常在a标签的文本或aria-label里链接是href属性头像在img的src。这里有个细节部分卡片是推荐好友而非真实好友链接里会带?ref参数需要过滤掉。import csv import re def extract_friends(cards): seen set() rows [] for card in cards: try: href card.get_attribute(href) or # 过滤推荐位和空链接 if friend not in href or refbookmarks in href: continue # 用主页 ID 去重 match re.search(rprofile\.php\?id(\d), href) uid match.group(1) if match else href.split(?)[0] if uid in seen: continue seen.add(uid) name card.text.strip() or card.get_attribute(aria-label) or 未知 # 头像通常在卡片内的 img 标签 try: img card.find_element(By.TAG_NAME, img) avatar img.get_attribute(src) or except Exception: avatar rows.append({昵称: name, 主页: href, 头像: avatar}) except Exception as e: print(f跳过一条异常卡片: {e}) return rows def save_csv(rows, pathfriends.csv): if not rows: print(没有可写入的数据) return with open(path, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnames[昵称, 主页, 头像]) writer.writeheader() writer.writerows(rows) print(f已写入 {len(rows)} 条到 {path})逻辑说明seen集合按用户 ID 去重避免滚动过程中同一张卡片被重复采集utf-8-sig编码让 Excel 打开 CSV 时不乱码字段顺序固定为昵称、主页、头像方便后续用 pandas 读取。如果某条卡片解析失败打印异常并继续不让单条错误中断整个任务。4. 避坑与排查那些让我重跑三次的细节4.1 登录态失效导致页面直接跳转现象脚本启动后浏览器打开的是登录页所有选择器都找不到元素报超时。原因Selenium 每次启动的是全新会话不带任何 Cookie。解决要么在启动参数里加--user-data-dir指向已登录的浏览器配置目录要么在脚本开头留出 30 秒手动扫码时间用input(扫码后按回车继续)阻塞。我一般用前者但注意配置目录不能被日常浏览器占用。4.2 滚动到底部后卡片数不再增长但实际还有数据现象连续三次滚动卡片数不变脚本停止但手动打开页面发现下面还有内容。原因平台的懒加载有节流快速滚动时新内容还没请求回来导致计数稳定假象。解决把stable_rounds提高到 5或者在每次滚动后额外等一个网络空闲信号比如driver.execute_script(return document.readyState)返回complete再计数。更稳妥的是在停顿区间里加一个 5 秒的长停顿每 10 次滚动触发一次。4.3 选择器命中大量非好友元素现象抓下来的 CSV 里混进了“推荐好友”“你可能认识的人”甚至广告卡片。原因候选选择器写得太宽泛a[href*friend]会把所有带 friend 字样的链接都捞进来。解决在提取阶段加过滤条件检查链接是否包含profile.php?id或/friends/路径同时排除带refbookmarks、refsearch的推荐位。更彻底的办法是先用开发者工具确认真实好友卡片的父容器有唯一属性再缩小选择器范围。4.4 头像地址是缩略图或空字符串现象CSV 里头像列大量为空或者全是 50x50 的小图。原因列表页为了性能只加载缩略图部分卡片甚至用懒加载占位符src是 base64 或空。解决如果只需要头像链接接受缩略图即可如果需要原图得再进每个主页抓一次但那会大幅增加请求量和风控风险。我的建议是列表页只存缩略图地址后续按需异步补全不要在主流程里做。4.5 运行中途被要求验证身份或限制访问现象脚本跑了几十次滚动后页面突然弹出验证码或提示“操作过于频繁”。原因滚动节奏太机械或者单次会话抓取量过大。解决把pause_range下限提高到 2.5 秒单次会话最多抓 200 条就主动退出换时段再跑。另外不要在同一个账号上高频运行准备一个专门用于数据采集的账号降低主账号风险。5. 进阶技巧把一次性脚本变成可复用的采集管道5.1 用配置文件管理选择器和滚动参数硬编码选择器的脚本活不过两次改版。我习惯把选择器、滚动次数、停顿区间写进一个config.yaml脚本启动时读取。这样改版时只需要改配置不用动代码。import yaml def load_config(pathconfig.yaml): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) # config.yaml 示例内容 # selectors: # card: div[rolemain] a[href*friend] # name: span # avatar: img # scroll: # max_scrolls: 60 # pause_min: 2.0 # pause_max: 4.0 # stable_rounds: 5参数说明card是卡片选择器name和avatar是卡片内子元素选择器max_scrolls控制上限pause_min和pause_max给随机停顿范围stable_rounds决定何时停止。把配置和代码分离后同一套逻辑可以快速适配不同页面结构。5.2 断点续抓记录已采集 ID 避免重复劳动如果一次没抓完下次重跑时不应该从头再来。我的做法是在 CSV 旁边维护一个seen_ids.txt每采集一条就追加写入用户 ID。启动时先加载这个文件到集合里提取阶段直接跳过已存在的 ID。def load_seen_ids(pathseen_ids.txt): try: with open(path, r, encodingutf-8) as f: return set(line.strip() for line in f if line.strip()) except FileNotFoundError: return set() def append_seen_id(uid, pathseen_ids.txt): with open(path, a, encodingutf-8) as f: f.write(uid \n)逻辑说明load_seen_ids在脚本启动时调用一次把历史 ID 读进内存append_seen_id在每提取一条新记录后调用实时落盘。这样即使脚本中途崩溃下次也能接着跑。注意文件写入用追加模式不要每次重写整个文件否则数据量大时会很慢。5.3 用 pandas 做后处理去重、排序与导出 ExcelCSV 只是中间产物最终交付往往需要 Excel 或数据库。用 pandas 读进来做一次全局去重和排序再导出成.xlsx比在采集阶段做更高效。import pandas as pd def postprocess(csv_pathfriends.csv, xlsx_pathfriends.xlsx): df pd.read_csv(csv_path, encodingutf-8-sig) # 按主页链接去重保留第一条 df df.drop_duplicates(subset[主页], keepfirst) # 按昵称排序方便查阅 df df.sort_values(by昵称, ascendingTrue) # 重置索引 df df.reset_index(dropTrue) df.to_excel(xlsx_path, indexFalse) print(f后处理完成共 {len(df)} 条已导出到 {xlsx_path}) return df参数说明drop_duplicates的subset指定按主页链接判重keepfirst保留首次出现的记录sort_values按昵称排序如果昵称包含多语言字符排序结果可能不符合预期可以改用主页 ID 排序to_excel需要额外安装openpyxl用pip install openpyxl即可。这一步放在采集之后不占用浏览器会话时间适合批量处理多个 CSV 文件。5.4 一个我常犯的错误忽略浏览器版本与驱动匹配最后说个血泪教训。有次在另一台机器上跑脚本webdriver-manager下载的驱动版本和本机浏览器差了一个大版本启动直接报SessionNotCreatedException。排查了半天才发现是浏览器自动更新了而驱动缓存没刷新。后来我养成的习惯是每次换机器先手动跑一次python -c from webdriver_manager.chrome import ChromeDriverManager; print(ChromeDriverManager().install())确认驱动路径和版本再启动主脚本。另外如果公司网络对驱动下载有限制提前把驱动放到本地路径用Service(executable_path...)指定别等到运行时才发现下不动。希望帮到你。本文还有配套的精品资源点击获取