ARTICLE DETAIL

资讯详情

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

Facebook爬虫实战:用requests+selenium搞定动态页面数据采集与TaoToken配置

Facebook爬虫实战:用requests+selenium搞定动态页面数据采集与TaoToken配置 1. Facebook 动态页面采集为什么 requests 直接抓不到Facebook 的公开主页、时间线、好友列表这些内容本质上都是前端 JS 渲染出来的。你打开浏览器开发者工具看 Network会发现首屏 HTML 里几乎没有你要的数据真正的内容是通过 XHR/fetch 异步拉回来的。这就是为什么很多人第一次写爬虫用 requests.get 拿到页面源码后用 BeautifulSoup 一解析发现全是空壳。我试过最直接的方式requests.get(https://m.facebook.com/profile.php?idxxx)返回的 HTML 里确实有一些基础结构但时间线内容、图片列表、视频这些动态加载的部分全部缺失。原因在于 Facebook 的移动端站点 m.facebook.com 虽然比 www 站轻量但核心数据依然依赖 JS 执行后的二次请求。那 selenium 能不能解决能但慢。selenium 启动浏览器、等待 JS 渲染、滚动加载每一步都是秒级开销。如果你要采集几十个用户的历史发表、图片、好友、视频、个人资料、签到纯 selenium 跑下来时间成本极高。所以更合理的方案是混合模式用 selenium 完成登录和获取关键凭证cookies、token、心跳包参数然后用 requests 复用这些凭证去批量请求数据接口。这里有个关键点m.facebook.com 和 www.facebook.com 共用 cookies。也就是说你在 selenium 里登录 m 站拿到的 cookies 可以直接用于 requests 请求 www 站或 m 站的接口。这个特性让混合采集变得可行。具体到动态页面数据采集Facebook 的时间线请求走的是这样一个接口https://m.facebook.com/profile/timeline/stream/?cursortmln_strm%3A1341235186%3A4123521292106084490%3A0profile_id100003102976600replace_idu_z_0抓包会发现这个请求原本是 POST参数很复杂。但多次尝试后可以确认通过 GET 请求也能拿到数据前提是你得先拿到 replace_id。而 replace_id 来自上一个请求的 __req 参数__req 又在心跳包里不停变化。心跳包长这样GET https://edge-chat.facebook.com/pull?channelp_100003102976600seq1clientid4166e2a6profilemobilepartition-2sticky_token588msgs_recv1qpycb2838170899stateactivesticky_poolash4c09_chat-proxyuid100003102976600viewer_uid100003102976600m_sess__dyn...__req13__ajax__...__user100003102976600响应是for (;;); {t:fullReload,seq:1}。同时还有 sub 请求POST https://edge-chat.facebook.com/sub?cblfnhsticky_token588uid100003102976600viewer_uid100003102976600sticky_poolash4c09_chat-proxyprofilemobileclientid4166e2a6cap0响应是for (;;); {t:pong}。两个 sub 请求的差别只在 cb 参数上。这意味着你必须模拟心跳包发送到 Facebook 服务器触发这个心跳包的可能是一个本地 setInterval 函数。在本地调试 JS 时可以看到生成的链接代码Q.prototype.getURI function() { use strict; return this._uri }最外面的数字依次增大但不是顺序的右边的代码是固定的13、16 代表请求数据还是心跳包。破解思路就是模拟发送请求将心跳包参数递增。这套逻辑用 requests 完全可以复现但前提是你得先有一个稳定的请求凭证管理通道。这就是 TaoToken 介入的地方——它不直接帮你爬 Facebook而是帮你统一管理 API 请求的凭证、Base URL 和模型调用让你在写采集脚本时不用把 key 硬编码在代码里也方便后续接入 AI 做数据清洗或内容分析。2. TaoToken 前置准备统一 API 通道与凭证管理在开始写采集脚本之前先把请求凭证的管理通道搭好。TaoToken 在这里的角色是给你一个统一的 API 入口把模型调用、coding plan、console 管理、api-keys 这些能力集中起来。你采集到的 Facebook 数据后续如果要接 AI 做摘要、分类、翻译就可以直接走这个通道不用再单独维护一套 key。第一步打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册并登录。登录后进入 console 页面地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 。在 console 里你可以看到当前账号的额度、调用记录、以及可用的模型列表。第二步创建 API Key。进入 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 点击创建新的 key。这个 key 就是你后续在采集脚本里调用 AI 能力时用的凭证。注意不要把 key 直接写死在代码里建议用环境变量或者配置文件管理。第三步确认 API Base URL。TaoToken 的 API 入口是 https://taotoken.net/api 这个地址不加 UTM 参数直接用于代码里的 base_url 配置。如果你用的是 OpenAI 兼容的 SDK就把 base_url 设成这个。第四步如果你打算长期做编码类任务或者 Agent 开发可以看一下 Coding Plan 页面https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。这个计划适合需要持续调用模型做代码生成、数据处理的场景。第五步模型对话调试。在正式写脚本之前可以先在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 里测试一下模型是否可用确认你的 key 和 base_url 配置正确。第六步接入文档。如果你需要更详细的参数说明看 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。里面会说明各个接口的请求格式、返回结构、错误码。如果你用的是 Claude Code 或者类似的编码工具可以参考 https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude-codeutm_campaignrewrite 里的配置说明。这里要强调一点TaoToken 不是用来替代你的爬虫逻辑的它解决的是「请求凭证统一管理」和「AI 能力接入」的问题。你的 Facebook 采集还是用 requests selenium但采集到的数据要清洗、要分析、要生成报告时就可以通过 TaoToken 的 API 通道调用模型。配置示例Python 环境变量方式import os # TaoToken API 配置 TAOTOKEN_BASE_URL https://taotoken.net/api TAOTOKEN_API_KEY os.getenv(TAOTOKEN_API_KEY, sk-你的key) # 使用 OpenAI 兼容 SDK from openai import OpenAI client OpenAI( base_urlTAOTOKEN_BASE_URL, api_keyTAOTOKEN_API_KEY ) response client.chat.completions.create( modelgpt-4o-mini, messages[ {role: user, content: 帮我总结这段 Facebook 采集数据的关键信息} ] ) print(response.choices[0].message.content)如果你用的是配置文件方式可以写一个config.json{ taotoken: { base_url: https://taotoken.net/api, api_key: sk-你的key, model: gpt-4o-mini }, facebook: { profile_id: 100003102976600, cookie_file: fb_cookies.json } }这样你的采集脚本和 AI 调用就解耦了后续换 key 或者换模型只需要改配置文件。3. 可复制配置requests selenium 混合采集脚本这一节直接给可复制的代码。整个流程分三步selenium 登录并保存 cookiesrequests 复用 cookies 请求时间线接口解析返回的 JSON 数据。先装依赖pip install requests selenium beautifulsoup4 lxml openaiselenium 需要对应的浏览器驱动Chrome 的话下载 chromedriver 放到 PATH 里。第一步selenium 登录并保存 cookiesimport json import time from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC def login_and_save_cookies(email, password, cookie_filefb_cookies.json): options webdriver.ChromeOptions() options.add_argument(--disable-blink-featuresAutomationControlled) options.add_argument(--user-agentMozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15) driver webdriver.Chrome(optionsoptions) driver.get(https://m.facebook.com/login) wait WebDriverWait(driver, 20) email_input wait.until(EC.presence_of_element_located((By.NAME, email))) email_input.send_keys(email) password_input driver.find_element(By.NAME, pass) password_input.send_keys(password) login_btn driver.find_element(By.NAME, login) login_btn.click() time.sleep(10) cookies driver.get_cookies() with open(cookie_file, w) as f: json.dump(cookies, f) driver.quit() return cookies if __name__ __main__: login_and_save_cookies(your_email, your_password)第二步requests 复用 cookies 请求时间线import json import requests from requests.cookies import RequestsCookieJar def load_cookies(cookie_filefb_cookies.json): with open(cookie_file, r) as f: cookies json.load(f) jar RequestsCookieJar() for c in cookies: jar.set(c[name], c[value], domainc.get(domain, .facebook.com)) return jar def fetch_timeline(profile_id, cookie_filefb_cookies.json): jar load_cookies(cookie_file) headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15, Accept: application/json, text/javascript, */*; q0.01, X-Requested-With: XMLHttpRequest, Referer: fhttps://m.facebook.com/profile.php?id{profile_id} } url fhttps://m.facebook.com/profile/timeline/stream/ params { cursor: tmln_strm:1341235186:4123521292106084490:0, profile_id: profile_id, replace_id: u_z_0 } resp requests.get(url, headersheaders, cookiesjar, paramsparams, timeout30) return resp.text if __name__ __main__: html fetch_timeline(100003102976600) print(html[:2000])第三步解析返回数据。Facebook 返回的通常是for (;;);开头的 JSON需要先去掉前缀import re from bs4 import BeautifulSoup def parse_timeline_response(raw_text): if raw_text.startswith(for (;;);): raw_text raw_text[len(for (;;);):] try: data json.loads(raw_text) except json.JSONDecodeError: soup BeautifulSoup(raw_text, lxml) posts soup.find_all(div, class_re.compile(story|post)) return [p.get_text(stripTrue) for p in posts] return data if __name__ __main__: raw fetch_timeline(100003102976600) parsed parse_timeline_response(raw) print(json.dumps(parsed, ensure_asciiFalse, indent2)[:3000])第四步心跳包参数递增模拟。这部分是解决 __req 变化的关键import time import random class HeartbeatSimulator: def __init__(self, profile_id, clientid, sticky_token, sticky_pool): self.profile_id profile_id self.clientid clientid self.sticky_token sticky_token self.sticky_pool sticky_pool self.seq 1 self.req_counter 13 def next_req(self): self.req_counter random.choice([1, 2, 3]) return self.req_counter def build_pull_url(self): cb random.randint(1000000000, 9999999999) return ( fhttps://edge-chat.facebook.com/pull? fchannelp_{self.profile_id}seq{self.seq} fclientid{self.clientid}profilemobilepartition-2 fsticky_token{self.sticky_token}msgs_recv1qpy fcb{cb}stateactivesticky_pool{self.sticky_pool} fuid{self.profile_id}viewer_uid{self.profile_id} fm_sess__req{self.next_req()}__user{self.profile_id} ) def send_heartbeat(self, jar): url self.build_pull_url() headers { User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X), Accept: */* } resp requests.get(url, headersheaders, cookiesjar, timeout30) self.seq 1 return resp.text这套配置的核心逻辑是selenium 只负责登录和拿 cookies后续所有数据请求都用 requests 完成。心跳包模拟是为了维持会话活跃避免 cookies 过期。4. 验证请求与采集成功率检查代码写完了怎么确认采集真的成功了这一节给具体的验证步骤。第一步检查 cookies 是否有效。请求一个已知的公开主页看返回状态码def check_cookie_valid(cookie_filefb_cookies.json): jar load_cookies(cookie_file) resp requests.get( https://m.facebook.com/profile.php?id100003102976600, cookiesjar, headers{User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X)}, timeout30 ) if resp.status_code 200 and login not in resp.url: print(cookies 有效) return True else: print(fcookies 失效状态码{resp.status_code}跳转{resp.url}) return False第二步检查时间线接口返回内容。正常返回应该包含for (;;);前缀和 JSON 数据def verify_timeline(profile_id): raw fetch_timeline(profile_id) if for (;;); in raw: print(时间线接口返回正常) parsed parse_timeline_response(raw) if isinstance(parsed, dict): print(f返回字段{list(parsed.keys())}) elif isinstance(parsed, list): print(f解析到 {len(parsed)} 条动态) return True else: print(时间线接口返回异常前 500 字符) print(raw[:500]) return False第三步统计采集成功率。批量跑多个 profile_id记录成功和失败的数量def batch_collect(profile_ids, cookie_filefb_cookies.json): success 0 fail 0 results [] for pid in profile_ids: try: raw fetch_timeline(pid, cookie_file) if for (;;); in raw: success 1 results.append({profile_id: pid, status: ok}) else: fail 1 results.append({profile_id: pid, status: empty}) except Exception as e: fail 1 results.append({profile_id: pid, status: error, msg: str(e)}) time.sleep(random.uniform(2, 5)) total success fail rate success / total * 100 if total 0 else 0 print(f总数{total}成功{success}失败{fail}成功率{rate:.2f}%) return results第四步用 TaoToken 做数据后处理验证。采集到的数据如果要做摘要或分类可以调模型def summarize_with_taotoken(text): client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.getenv(TAOTOKEN_API_KEY) ) resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是一个数据清洗助手请提取以下文本的关键信息。}, {role: user, content: text[:2000]} ] ) return resp.choices[0].message.content实测下来cookies 有效期通常在几小时到一天左右。如果采集过程中出现大量失败优先检查 cookies 是否过期重新跑一遍 selenium 登录即可。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节对照真实报错给排查路径。报错一401 Unauthorized如果你在调 TaoToken API 时看到 401通常是 key 不对或者没传。检查# 错误写法key 为空 client OpenAI(base_urlhttps://taotoken.net/api, api_key) # 正确写法从环境变量读取 client OpenAI(base_urlhttps://taotoken.net/api, api_keyos.getenv(TAOTOKEN_API_KEY))确认 key 是在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 里创建的并且没有多余空格。报错二local proxy failed这个报错通常出现在网络请求层说明请求没有正常到达目标。检查你的 requests 配置里有没有设置 proxies如果有去掉# 不要设置 proxies resp requests.get(url, headersheaders, cookiesjar, timeout30)同时确认 base_url 写的是https://taotoken.net/api不要加多余的路径。报错三reading choices 相关错误如果你在解析模型返回时看到choices读取失败通常是返回结构不对。打印完整响应resp client.chat.completions.create(...) print(resp.model_dump_json(indent2))确认resp.choices[0].message.content存在。如果返回的是流式响应需要加streamTrue并逐块读取。报错四OAuth 相关错误如果你用的是 Claude Code 或类似工具配置里出现 OAuth 报错检查三件套是否齐全{ base_url: https://taotoken.net/api, api_key: sk-你的key, model: claude-3-5-sonnet }Base URL、Key、Model ID 三个都要写对。Model ID 参考 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里的模型列表。报错五Facebook 返回空数据如果 requests 返回 200 但内容为空检查cookies 是否过期重新登录headers 里的 User-Agent 是否和登录时一致replace_id 参数是否正确这个值来自上一个请求的 __req请求频率是否过高加 sleep 随机延迟报错六心跳包参数不递增导致会话断开如果采集一段时间后突然全部失败大概率是心跳包没发。检查 HeartbeatSimulator 的 seq 和 req_counter 是否在递增每次请求后要更新。6. 采集数据接入 AI 分析的完整链路采集只是第一步数据拿到之后怎么用才是关键。这一节把 requests selenium 采集和 TaoToken 的 AI 能力串起来。完整链路是这样的selenium 登录拿 cookies → requests 批量采集时间线数据 → 数据清洗去重 → 通过 TaoToken API 调用模型做摘要/分类/翻译 → 结果落库或导出。数据清洗部分def clean_posts(raw_posts): cleaned [] seen set() for post in raw_posts: text post.get(text, ).strip() if not text or text in seen: continue seen.add(text) cleaned.append({ text: text, timestamp: post.get(timestamp), likes: post.get(likes, 0) }) return cleaned批量调模型做摘要def batch_summarize(posts, batch_size10): results [] for i in range(0, len(posts), batch_size): batch posts[i:ibatch_size] combined \n---\n.join([p[text] for p in batch]) client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.getenv(TAOTOKEN_API_KEY) ) resp client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 请对以下多条动态分别生成一句话摘要用 JSON 数组返回。}, {role: user, content: combined} ] ) results.append(resp.choices[0].message.content) time.sleep(1) return results如果你需要长期跑这套采集 分析流程建议看一下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它适合需要持续调用模型做代码生成和数据处理的场景。模型对话调试可以在 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 里先试确认 prompt 效果后再写进脚本。API Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。最后说一个实际踩过的坑Facebook 的 replace_id 和 __req 是联动的如果你只递增 __req 但不更新 replace_id时间线接口会返回空。正确的做法是每次心跳包请求后从响应里提取新的 replace_id再带到下一个时间线请求里。这个细节在文档里不会写只能靠抓包对比发现。
返回列表