简介:这是一份面向Python爬虫初学者与自动化测试爱好者的Facebook好友列表抓取脚本,基于Python3编写,借助Selenium驱动Firefox浏览器自动完成登录并提取用户账户中的好友数据,只需填入邮箱与密码即可运行,适合想学习浏览器自动化、登录流程模拟与页面数据解析的开发者练手。资源包共6个文件,压缩后约4.03MB,包含1个核心py脚本、2个md说明文档、1个gif演示动图,以及license与gitignore等辅助文件,脚本通过argparse提供超时、登录文件、HTML页面等参数选项,并配合BeautifulSoup4解析页面内容。目前已有2889人学习下载,可帮助读者理解Selenium与Firefox的联动方式、登录态维持思路及抓取脚本的基本结构,并作为二次开发与排错调试的参考起点。
1. 朋友列表采集:一个被低估的工程问题
很多人第一次听到「用 Python 脚本抓取社交平台好友列表」时,脑子里浮现的画面是:打开浏览器,登录,点几下,数据就出来了。真动手才发现,这件事的复杂度根本不在「抓」这个动作上,而在登录态维持、动态加载、反自动化检测和数据结构解析这四个环节的配合上。selenium_facebook_scraper这个标题指向的正是这样一类脚本:用 Selenium 驱动真实浏览器,在已登录的会话里滚动好友列表弹窗,把用户名和主页链接逐条提取出来,最终落成一份可用的 CSV 或 JSON。
它适合谁?做社交关系分析的研究者、需要批量整理公开联系人信息的运营人员、以及想学 Selenium 实战但找不到合适练手项目的开发者。不适合谁?想绕过登录直接爬公开数据的人——好友列表本身是登录后才可见的内容,脚本必须建立在你自己账号的正常会话之上。这篇文章会把整个链路拆开:环境怎么搭、登录态怎么保、滚动加载怎么触发、数据怎么解析、以及最关键的——哪些地方会让你白跑一晚上。
2. 环境搭建与登录态维持:别让脚本死在第一步
2.1 Selenium 4 与浏览器驱动的版本对齐
Selenium 4 之后,驱动的管理方式变了。以前你得手动下载 chromedriver 放到 PATH 里,版本对不上就报SessionNotCreatedException。现在推荐用webdriver-manager自动匹配,省掉大量玄学问题。
pip install selenium webdriver-manager pandasfrom 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() # 关键:保持浏览器窗口可见,部分平台的登录流程对 headless 有额外校验 # options.add_argument("--headless=new") # 调试阶段先注释掉 options.add_argument("--disable-blink-features=AutomationControlled") options.add_experimental_option("excludeSwitches", ["enable-automation"]) options.add_experimental_option("useAutomationExtension", False) options.add_argument("--window-size=1280,900") options.add_argument("--lang=zh-CN") service = Service(ChromeDriverManager().install()) driver = webdriver.Chrome(service=service, options=options) # 抹掉 navigator.webdriver 标记,降低被识别为自动化的概率 driver.execute_cdp_cmd( "Page.addScriptToEvaluateOnNewDocument", {"source": "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"} )这段代码做了三件事:自动安装匹配版本的驱动、关闭「自动化控制」的显式标记、通过 CDP 注入脚本隐藏navigator.webdriver。参数说明上,--disable-blink-features=AutomationControlled是最常被忽略的一条,不加的话某些检测脚本一眼就能看出你在用 Selenium。--window-size建议设成常见分辨率,太小的窗口会导致好友列表弹窗的滚动容器高度异常,影响后续滚动逻辑。
2.2 登录态:手动登录 + Cookie 复用
最稳的做法不是让脚本自动填账号密码——那会触发验证码甚至风控。正确姿势是:第一次手动登录,把 Cookie 存下来,后续脚本直接加载 Cookie。
import time import pickle import os COOKIE_FILE = "cookies.pkl" def login_and_save_cookie(driver, wait_seconds=60): """首次运行:手动登录后回车,保存 Cookie""" driver.get("https://www.facebook.com/") print(f"请在 {wait_seconds} 秒内完成手动登录,然后回到终端按回车...") time.sleep(wait_seconds) input("登录完成后按回车继续...") with open(COOKIE_FILE, "wb") as f: pickle.dump(driver.get_cookies(), f) print(f"Cookie 已保存,共 {len(driver.get_cookies())} 条") def load_cookie(driver): """后续运行:加载已保存的 Cookie""" if not os.path.exists(COOKIE_FILE): return False driver.get("https://www.facebook.com/") with open(COOKIE_FILE, "rb") as f: cookies = pickle.load(f) for c in cookies: # 部分字段在新版本 Selenium 中不被接受,需要剔除 c.pop("sameSite", None) c.pop("expiry", None) try: driver.add_cookie(c) except Exception as e: print(f"跳过一条 Cookie: {e}") driver.refresh() return True逻辑说明:add_cookie对字段很挑剔,sameSite和expiry在部分版本会直接抛异常,所以先 pop 掉再写入。加载完必须refresh(),否则当前页面不会带上新 Cookie。判断是否登录成功,不要只看 URL,要检查页面上是否出现了只有登录后才有的元素,比如导航栏的头像区域。
提示:Cookie 有有效期,通常几天到几周不等。脚本跑之前先做一次登录态校验,失效了就重新走手动登录流程,别硬跑。
3. 好友列表的加载机制与滚动采集实现
3.1 弹窗式列表:为什么直接抓 DOM 只能拿到前几条
好友列表不是独立页面,而是一个模态弹窗。打开后,DOM 里只渲染当前视口附近的若干条,往下滚动才会动态追加。这意味着你find_elements一次,最多拿到十几条,剩下的根本没进 DOM。这是新手最常见的翻车点:脚本跑完显示「采集到 12 个好友」,然后一脸懵。
正确的做法是定位滚动容器,循环执行「滚动到底部 → 等待新元素加载 → 检查是否还有新内容」,直到数量不再增长。
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def open_friends_dialog(driver, profile_url): """打开目标用户主页并点击好友入口""" driver.get(profile_url) WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.XPATH, "//a[contains(@href, '/friends')]")) ) friends_link = driver.find_element(By.XPATH, "//a[contains(@href, '/friends')]") friends_link.click() # 等待弹窗容器出现 WebDriverWait(driver, 15).until( EC.presence_of_element_located((By.XPATH, "//div[@role='dialog']")) )这里用role='dialog'定位弹窗,比依赖具体 class 名稳定得多——社交平台的 class 名经常带哈希后缀,今天能用明天就变。定位策略上,优先用role、aria-label这类语义属性,其次才是结构化的 XPath。
3.2 滚动容器的定位与「滚不动」的排查
滚动容器通常是弹窗内部的一个div,带overflow: auto或overflow-y: scroll。定位它的可靠方式是:在弹窗内找到包含好友条目的最近的可滚动祖先。
def scroll_dialog_until_end(driver, max_rounds=200, pause=1.5): """在弹窗内反复滚动,直到没有新条目加载""" dialog = driver.find_element(By.XPATH, "//div[@role='dialog']") # 找到弹窗内实际可滚动的容器 scroll_box = dialog.find_element( By.XPATH, ".//div[contains(@style, 'overflow') or @tabindex='-1']" ) last_count = 0 stable_rounds = 0 for i in range(max_rounds): # 用 JS 直接操作 scrollTop,比 send_keys 更可靠 driver.execute_script( "arguments[0].scrollTop = arguments[0].scrollHeight", scroll_box ) time.sleep(pause) items = dialog.find_elements(By.XPATH, ".//a[contains(@href, 'facebook.com')]") current = len(items) print(f"第 {i+1} 轮滚动,当前条目数:{current}") if current == last_count: stable_rounds += 1 if stable_rounds >= 3: # 连续 3 轮无增长,判定到底 print("已到达列表底部") break else: stable_rounds = 0 last_count = current return last_count参数说明:pause设 1.5 秒是经验值,网络慢的时候可以调到 2.5 秒;stable_rounds >= 3是防止某次加载延迟导致误判到底。用execute_script直接设scrollTop比send_keys(Keys.END)稳定,因为后者依赖焦点,弹窗失焦就失效了。
注意:如果滚动几轮后条目数完全不变,先检查
scroll_box是不是找错了元素。可以在浏览器控制台手动执行document.querySelectorAll('[role=dialog] div')逐个看哪个的scrollHeight > clientHeight。
3.3 数据解析:从 DOM 到结构化记录
好友条目里通常包含头像、姓名、主页链接,有时还有「共同好友数」。解析时用相对 XPath,从每个条目元素往下找,避免全局匹配串数据。
import pandas as pd def parse_friends(driver): dialog = driver.find_element(By.XPATH, "//div[@role='dialog']") # 每个好友条目一般是一个包含头像链接的容器 rows = dialog.find_elements(By.XPATH, ".//div[@role='listitem']") records = [] for r in rows: try: link_el = r.find_element(By.XPATH, ".//a[contains(@href, 'facebook.com')]") name = link_el.text.strip() href = link_el.get_attribute("href") # 过滤掉非用户链接(如「查看更多」) if not name or "/friends" in href: continue records.append({"name": name, "profile_url": href.split("?")[0]}) except Exception: continue # 去重 df = pd.DataFrame(records).drop_duplicates(subset=["profile_url"]) return df # 主流程 if __name__ == "__main__": if not load_cookie(driver): login_and_save_cookie(driver) open_friends_dialog(driver, "https://www.facebook.com/你的目标用户名/friends") scroll_dialog_until_end(driver) df = parse_friends(driver) df.to_csv("friends.csv", index=False, encoding="utf-8-sig") print(f"共采集 {len(df)} 条记录")encoding="utf-8-sig"是为了 Excel 打开不乱码。href.split("?")[0]去掉追踪参数,方便后续去重。解析阶段用 try/except 包住每条,单条失败不影响整体——这是批量采集的基本素养。
4. 避坑与排查:那些让你白跑一晚上的问题
4.1 现象:脚本启动后浏览器闪退或报驱动版本错误
原因:webdriver-manager下载的驱动版本和本机 Chrome 大版本不匹配,或者公司网络拦截了驱动下载。解决:先确认本机 Chrome 版本(设置 → 关于 Chrome),手动指定驱动路径;如果下载被拦截,提前把驱动放到本地目录,用Service(executable_path=...)直接指定。
4.2 现象:登录后刷新页面又回到登录页
原因:Cookie 写入时带了sameSite或expiry字段导致部分 Cookie 被拒,或者add_cookie在错误的域名下执行。解决:先driver.get到目标域名再写 Cookie;写入前 pop 掉不兼容字段;写完必须refresh()。如果还不行,检查是不是有 HttpOnly 的 Cookie 没被get_cookies()拿到——这种情况需要改用浏览器 profile 目录复用登录态。
4.3 现象:滚动很多轮,条目数卡在某个值不动
原因:滚动容器定位错了,实际滚动的是外层页面而不是弹窗内部;或者触发了平台的加载频率限制,新内容不再返回。解决:用 JS 打印候选容器的scrollHeight和clientHeight,确认哪个真正可滚;如果是频率限制,把pause调大到 3~5 秒,并在每轮之间加随机抖动。
4.4 现象:采集到的姓名大量为空或重复
原因:XPath 匹配到了头像链接、按钮等非姓名元素;或者列表虚拟化导致同一元素被反复读取。解决:解析时增加过滤条件(排除空文本、排除含/friends的链接),并在最终结果按profile_url去重。虚拟化列表的场景下,建议边滚动边增量收集,而不是滚到底再统一解析。
4.5 现象:跑了几百条后被要求验证身份
原因:短时间内高频滚动和请求触发了风控。解决:控制单次采集量,加入随机等待,避免固定间隔;不要用同一个账号 7×24 小时跑。这是工程边界问题,不是代码能完全绕过的——采集频率要克制,把它当成「辅助整理」而不是「批量抓取」。
5. 进阶:把一次性脚本改成可复用的小工具
走到这里,你已经能跑通单次采集。但真实需求往往是「定期更新」「多个目标」「增量对比」。我的习惯是把脚本拆成三层:browser.py管驱动和登录态,scraper.py管滚动和解析,main.py管调度和存储。这样换目标、换输出格式都不用动核心逻辑。
一个具体技巧是增量采集:每次跑完把结果和上次的 CSV 做 diff,只记录新增和消失的好友。用 pandas 的merge加indicator参数,三行就能实现。
def diff_friends(old_csv, new_df): import pandas as pd old = pd.read_csv(old_csv) merged = old.merge(new_df, on="profile_url", how="outer", suffixes=("_old", "_new"), indicator=True) added = merged[merged["_merge"] == "right_only"] removed = merged[merged["_merge"] == "left_only"] return added, removed验证方法上,别只看条数。随机抽 5 条记录,手动打开主页确认链接有效、姓名对得上,这一步能筛掉大部分「看起来跑通了其实数据是错的」的情况。另外,把每次运行的日志(时间、目标、采集条数、耗时)写进一个run_log.csv,出问题时回溯非常方便。
最后说个血泪经验:这类脚本最大的成本不是写代码,而是调试选择器和处理登录态失效。我一般会在脚本里留一个DEBUG开关,打开时每步截图存盘,翻车的时候直接看截图比看日志快十倍。采集频率上,宁可慢一点、稳一点,也不要追求一次跑完——被风控拦一次,恢复的成本远高于多等几分钟。希望帮到你。
本文还有配套的精品资源,点击获取