Facebook用户信息爬虫技术分析与实现详解

前言

在当今数据驱动的时代,社交媒体数据的获取和分析变得越来越重要。本文将深入分析一个Facebook用户信息爬虫的实现原理,涵盖用户搜索、信息提取、并发处理等核心技术。

⚠️ 免责声明:本文仅供技术学习和研究使用,请遵守相关法律法规和平台使用条款。

技术架构概览

该爬虫系统主要由两个核心类组成:

  1. FacebookUserInfoSpider - 负责单个用户详细信息的爬取
  2. FacebookSearchUserSpider - 负责用户搜索和批量信息获取

核心技术分析

1. 请求头伪装技术

python 复制代码
__HTML_HEADERS__ = {
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9...",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
    "sec-ch-ua": "\"Google Chrome\";v=\"131\"...",
    # ... 更多头部信息
}

技术要点:

  • 完整模拟真实浏览器请求头
  • 包含Chrome浏览器特征信息
  • 设置合适的Accept和Language参数
  • 添加安全相关的sec-ch-*头部

2. Cookie管理机制

python 复制代码
def cookie_str_to_dict(self, cookie_str: str) -> dict:
    cookie_dict = {}
    cookies = [i.strip() for i in cookie_str.split('; ') if i.strip() != ""]
    for cookie in cookies:
        key, value = cookie.split('=', 1)
        cookie_dict[key] = value
    return cookie_dict

关键Cookie字段:

  • c_user: 用户ID标识
  • xs: 会话令牌
  • datr: 设备跟踪标识
  • fr: Facebook认证令牌

3. 动态Token获取

python 复制代码
def get_token(self):
    url = "https://www.facebook.com/"
    # ... 请求处理
    fb_dtsg = re.findall('"DTSGInitialData",\[\],\{"token":"(.*?)"\},', html)[0]
    return fb_dtsg, "0"

技术原理:

  • 通过正则表达式提取页面中的DTSG令牌
  • DTSG用于GraphQL API请求的CSRF保护
  • 动态获取确保令牌的有效性

4. GraphQL API调用

该爬虫使用Facebook的GraphQL接口进行数据获取:

python 复制代码
def get_data(self, cursor, keyword, fb_dtsg):
    variables = {
        "count": 5,
        "allow_streaming": False,
        "args": {
            "callsite": "COMET_GLOBAL_SEARCH",
            "config": {"exact_match": False, "high_confidence_config": None,
                       "intercept_config": None, "sts_disambiguation": None},
            "context": {"bsid": "d5226c60-92d1-45fc-b2b5-274daabf2bcb"},
            "experience": {
                "client_defined_experiences": ["ADS_PARALLEL_FETCH"],
                "type": "PEOPLE_TAB"
            },
            "filters": [],
            "text": keyword
        },
        "cursor": cursor,
        "__relay_internal__pv__GHLShouldChangeAdIdFieldNamerelayprovider": False,
        # ... 更多内部参数
    }
    
    data = {
        "av": self.cookies["c_user"],
        "__user": self.cookies["c_user"],
        "__req": "1b",
        "__hs": "20062.HYP:comet_pkg.2.1..2.1",
        "__rev": "1018642777",
        "fb_dtsg": fb_dtsg,
        "variables": json.dumps(variables, separators=(',', ":"), ensure_ascii=False),
        "doc_id": "28106583125599288"  # GraphQL查询ID
    }

关键技术点:

  • doc_id: Facebook GraphQL查询的唯一标识符
  • __rev: 客户端版本号,用于API兼容性
  • __hs: 哈希签名,用于请求验证
  • bsid: 浏览器会话ID
  • variables: JSON格式的查询参数

5. 复杂数据解析技术

JavaScript内嵌数据提取

Facebook将关键数据嵌入在页面的JavaScript代码中,需要通过特定的解析策略提取:

python 复制代码
def parse_data(self, html, url_):
    # 初始化数据结构
    item = {"profile_url": url_, "user_id": "", "user_name": "", ...}
    
    ele = etree.HTML(html)
    script_str_list = ele.xpath('//script/text()')
    
    # 第一阶段:解析profile_tile_section_type数据
    for script_str in script_str_list:
        if "profile_tile_section_type" in script_str:
            data_json = json.loads(script_str)
            requires = data_json["require"][0][3][0]["__bbox"]["require"]
            
            # 查找实际数据节点
            result = None
            for r in requires:
                if len(r) == 4 and r[1] == "next":
                    result = r[-1][1]["__bbox"]["result"]
                    break
            
            if result:
                # 提取用户ID和基本信息
                try:
                    user = result["data"]["user"]
                    item["user_id"] = user['id']
                    edges = user["profile_tile_sections"]["edges"]
                except:
                    # 备用数据路径
                    edges = result["data"]["profile_tile_sections"]["edges"]
                    item["user_id"] = result["data"]["id"]
                
                # 解析INTRO类型的信息块
                for e in edges:
                    if e['node']['profile_tile_section_type'] == "INTRO":
                        node_list = e['node']["profile_tile_views"]["nodes"]
                        break
                
                # 详细信息提取
                for n in node_list:
                    renderer = n["view_style_renderer"]
                    if renderer:
                        self._parse_renderer_data(renderer, item)
渲染器数据解析
python 复制代码
def _parse_renderer_data(self, renderer, item):
    typename = renderer['__typename']
    
    # 个人简介解析
    if typename == "ProfileTileViewIntroBioRenderer":
        bio_text = renderer["view"]["profile_tile_items"]["nodes"][0]["node"]["profile_status_text"]["text"]
        item["description"] = bio_text
    
    # 上下文列表信息解析
    elif typename == "ProfileTileViewContextListRenderer":
        nodes = renderer["view"]["profile_tile_items"]["nodes"]
        
        for node in nodes:
            context_item = node['node']['timeline_context_item']
            item_type = context_item['timeline_context_list_item_type']
            renderer_data = context_item['renderer']
            
            # 根据不同类型提取文本
            try:
                text = renderer_data["context_item"]["title"]['text']
            except:
                text = renderer_data.get('wa_number', '') if 'wa_number' in renderer_data else ""
            
            # 信息分类存储
            self._categorize_user_info(item_type, text, item)
信息分类处理
python 复制代码
def _categorize_user_info(self, item_type, text, item):
    """根据Facebook的内部类型标识分类存储用户信息"""
    
    type_mapping = {
        "INTRO_CARD_INFLUENCER_CATEGORY": "category",
        "INTRO_CARD_CONFIRMED_OWNER_LABEL": "person_charge",
        "INTRO_CARD_PROFILE_EMAIL": "email",
        "INTRO_CARD_WEBSITE": "website",
        "INTRO_CARD_CURRENT_CITY": "city",
        "INTRO_CARD_HOMETOWN": "from",
        "INTRO_CARD_ADDRESS": "address",
        "INTRO_CARD_BUSINESS_HOURS": "business_hour",
        "INTRO_CARD_RATING": "comment_count",
        "INTRO_CARD_WORK": "work",
        "INTRO_CARD_FOLLOWERS": "follower_count",
        "INTRO_CARD_RELATIONSHIP": "relationship"
    }
    
    # 特殊处理电话号码(包括WhatsApp)
    if item_type in ["INTRO_CARD_PROFILE_PHONE", "INTRO_CARD_PROFILE_WHATSAPP_NUMBER"]:
        if item.get("tel"):
            texts = item["tel"].split("; ")
            texts.append(text)
            item["tel"] = "; ".join(texts)
        else:
            item["tel"] = text
    
    # 特殊处理可能有多个值的字段
    elif item_type in ["INTRO_CARD_OTHER_ACCOUNT", "INTRO_CARD_EDUCATION"]:
        field_name = "other_account" if "OTHER_ACCOUNT" in item_type else "education"
        if item.get(field_name):
            existing_values = item[field_name].split("; ")
            existing_values.append(text)
            item[field_name] = "; ".join(existing_values)
        else:
            item[field_name] = text
    
    # 标准单值字段处理
    elif item_type in type_mapping:
        item[type_mapping[item_type]] = text
用户头部信息解析
python 复制代码
# 第二阶段:解析XFBProfileEntityConvergenceHeaderRenderer数据
if "XFBProfileEntityConvergenceHeaderRenderer" in script_str:
    data_json = json.loads(script_str)
    # ... 类似的数据定位逻辑
    
    if result:
        user = result["data"]["user"]["profile_header_renderer"]["user"]
        item["user_name"] = user.get('name', '')
        
        # 社交统计数据解析
        try:
            contents = user["profile_social_context"]["content"]
            for content in contents:
                uri = content.get('uri', '')
                text = content['text']['text']
                
                # 根据URI路径判断数据类型
                if "friends_likes" in uri:
                    item["like_count"] = text
                elif "followers" in uri:
                    item["follower_count"] = text
                elif "following" in uri:
                    item["following_count"] = text
                elif "friends" in uri and "like" not in uri:
                    item["friend_count"] = text
        except:
            pass

6. 并发处理与超时控制

python 复制代码
def main(self, keyword, cookie_str, cursor=None, fb_dtsg=None, timeout=30):
    # ... 初始化和数据获取
    
    resultList = self.parse_data(edges)  # 获取用户URL列表
    item_list = []
    
    # 关键:限制并发数为1,避免被检测
    with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
        # 使用partial预设超时参数
        run_with_timeout = partial(fbis.run)
        
        # 提交所有任务
        future_to_url = {
            executor.submit(run_with_timeout, url): url
            for url in resultList
        }
        
        # 处理完成的任务
        for future in concurrent.futures.as_completed(future_to_url):
            try:
                item, status_ = future.result(timeout=timeout)
                item_list.append(item)
                
                # 检查是否遇到严重错误
                if status_ == "-2":
                    return {
                        "cursor": cursor,
                        "token": fb_dtsg,
                        "item_list": [],
                        "status": status_
                    }
                    
            except (concurrent.futures.TimeoutError, 
                    requests.exceptions.Timeout):
                continue  # 超时的任务直接跳过
    
    return {
        "cursor": cursor,
        "token": fb_dtsg,
        "item_list": item_list,
        "status": "0"
    }

并发控制要点:

  • max_workers=1: 严格控制并发,模拟人工操作
  • 超时控制: 30秒超时避免长时间阻塞
  • 优雅降级: 超时任务跳过,不影响整体流程
  • 错误传播: 严重错误时立即停止后续处理

8. 搜索结果解析机制

python 复制代码
def parse_data(self, edges):
    """解析搜索结果中的用户链接"""
    resultList = []
    for e in edges:
        try:
            # 深度嵌套的数据结构解析
            vm = e['relay_rendering_strategy']['view_model']
            profile_url = vm['profile']['profile_url']
            resultList.append(profile_url)
        except Exception as e:
            print("解析错误:", e)
    return resultList

数据流向分析:

  1. GraphQL API返回搜索结果
  2. edges数组中提取每个用户的视图模型
  3. 通过relay_rendering_strategy获取渲染策略
  4. view_model.profile中提取用户主页URL

9. 会话状态管理

python 复制代码
class FacebookUserInfoSpider:
    def __init__(self, cookie_str):
        self.session = requests.Session()  # 复用TCP连接
        self.cookies = self.cookie_str_to_dict(cookie_str)
        
    def get(self, url):
        response = self.session.get(url,
                                   headers=self.headers,
                                   cookies=self.cookies,
                                   allow_redirects=False,  # 禁止自动重定向
                                   timeout=(5, 10))        # 连接和读取超时

会话管理要点:

  • 使用requests.Session()复用TCP连接
  • 禁止自动重定向避免意外跳转
  • 设置合理的超时时间
  • 保持Cookie状态一致性
python 复制代码
def get(self, url):
    while True:
        try:
            response = self.session.get(url, headers=self.headers, 
                                      cookies=self.cookies, timeout=(5, 10))
            # ... 处理响应
        except (ProxyError, ReadTimeout, ConnectionError) as e:
            logger.info("发生网络错误:", e)
            time.sleep(5)
        except Exception as e:
            logger.info("get发生错误:", e)
            return None, "-2"

错误处理策略:

  • 网络异常自动重试
  • 设置合理的重试间隔
  • 区分不同类型的错误状态码

关键技术细节补充

1. Facebook内部数据结构

Facebook使用了复杂的嵌套JSON结构来存储页面数据:

python 复制代码
# 典型的数据路径结构
data_path = {
    "用户基本信息": "require[0][3][0].__bbox.require[n][-1][1].__bbox.result.data.user",
    "个人简介": "profile_tile_views.nodes[].view_style_renderer.view.profile_tile_items.nodes[].node.profile_status_text.text",
    "联系信息": "timeline_context_item.renderer.context_item.title.text",
    "社交统计": "profile_header_renderer.user.profile_social_context.content[].text.text"
}

2. 关键请求参数详解

python 复制代码
# GraphQL请求的关键参数
critical_params = {
    "doc_id": "28106583125599288",  # Facebook内部查询ID,可能会变化
    "__rev": "1018642777",         # 客户端版本,需要匹配
    "__hs": "20062.HYP:comet_pkg.2.1..2.1",  # 哈希签名
    "jazoest": "25179",            # JavaScript生成的验证码
    "__spin_r": "1018642777",      # 旋转标识符
    "__spin_b": "trunk",           # 分支标识
    "__spin_t": "1733391682"       # 时间戳
}

3. 反爬虫对抗措施

python 复制代码
def get_realistic_headers():
    """构造更真实的请求头"""
    return {
        # 关键:模拟真实浏览器的完整特征
        "sec-ch-ua-full-version-list": "\"Google Chrome\";v=\"131.0.6778.86\"",
        "sec-ch-ua-platform-version": "\"8.0.0\"",
        "sec-fetch-dest": "document",
        "sec-fetch-mode": "navigate",
        "sec-fetch-site": "same-origin",
        "sec-fetch-user": "?1",
        "upgrade-insecure-requests": "1",
        "viewport-width": "848",  # 模拟视口宽度
        "dpr": "0.9"             # 设备像素比
    }

def handle_rate_limiting():
    """处理频率限制"""
    if "Rate limit exceeded" in response.text:
        # 实现指数退避策略
        wait_time = min(300, base_wait * (2 ** retry_count))
        time.sleep(wait_time)

用户信息数据模型

python 复制代码
user_info_schema = {
    "profile_url": "用户主页链接",
    "user_id": "用户数字ID",
    "user_name": "用户名称",
    "description": "个人简介",
    "category": "用户类别",
    "work": "工作信息",
    "education": "教育背景",
    "city": "当前城市",
    "from": "家乡",
    "email": "邮箱地址",
    "tel": "电话号码",
    "website": "个人网站",
    "follower_count": "粉丝数量",
    "friend_count": "好友数量"
}

状态码设计

python 复制代码
status_codes = {
    "0": "成功",
    "-1": "HTTP状态码异常",
    "-2": "系统错误或被限制"
}

完整的工作流程

主要执行流程

python 复制代码
def main(self, keyword, cookie_str, cursor=None, fb_dtsg=None, timeout=30):
    """完整的搜索和数据提取流程"""
    
    # 1. 初始化会话和Cookie
    self.cookies = self.cookie_str_to_dict(cookie_str)
    fbis = FacebookUserInfoSpider(cookie_str)
    
    try:
        # 2. 获取DTSG令牌(如果没有)
        if not fb_dtsg:
            fb_dtsg, _ = self.get_token()
            if not fb_dtsg:
                return {"cursor": cursor, "token": None, 
                       "item_list": [], "status": "-2"}
        
        # 3. 执行GraphQL搜索请求
        dataJson, _ = self.get(cursor, keyword, fb_dtsg)
        if not dataJson or not isinstance(dataJson, dict):
            return {"cursor": cursor, "token": fb_dtsg, 
                   "item_list": [], "status": "-2"}
        
        # 4. 提取搜索结果和分页信息
        edges = dataJson["data"]["serpResponse"]["results"]["edges"]
        cursor = dataJson["data"]["serpResponse"]["results"]["page_info"]["end_cursor"]
        
        if not edges:
            return {"cursor": cursor, "token": fb_dtsg, 
                   "item_list": [], "status": "0"}
        
        # 5. 解析用户URL列表
        resultList = self.parse_data(edges)
        
        # 6. 并发获取详细用户信息
        item_list = []
        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
            # 提交任务并处理结果
            # ... (前面已详细说明)
        
        # 7. 返回结果
        return {
            "cursor": cursor,      # 下一页的游标
            "token": fb_dtsg,      # 可复用的令牌
            "item_list": item_list, # 用户信息列表
            "status": "0"          # 执行状态
        }
        
    except Exception as e:
        print(f"Error occurred: {str(e)}")
        return {"cursor": cursor, "token": fb_dtsg, 
               "item_list": [], "status": "-2"}

数据流转示意

python 复制代码
用户搜索请求
    ↓
Cookie验证 + DTSG令牌获取
    ↓
GraphQL API调用 (搜索接口)
    ↓
搜索结果解析 (提取用户URL)
    ↓
并发用户详情获取 (个人主页解析)
    ↓
数据结构化输出
python 复制代码
# 初始化爬虫
fsus = FacebookSearchUserSpider()

# 设置Cookie(需要有效的登录Cookie)
cookie_str = 'sb=xxx; c_user=xxx; xs=xxx; ...'

# 执行搜索
cursor = None
fb_dtsg = None
page_count = 1

while page_count <= 5:  # 限制爬取页数
    result = fsus.main("food", cookie_str, cursor, fb_dtsg)
    
    cursor = result['cursor']
    fb_dtsg = result['token']
    
    print(f"第{page_count}页: 获取到{len(result['item_list'])}条数据")
    
    if result['status'] != '0':
        print("遇到错误,停止爬取")
        break
        
    page_count += 1

注意事项与建议

1. 合规使用

  • 遵守robots.txt协议
  • 控制请求频率,避免对服务器造成压力
  • 尊重平台的使用条款和用户隐私

2. 技术优化建议

  • 添加随机延时机制
  • 使用代理池分散请求
  • 实现更智能的重试策略
  • 加入数据去重机制

3. 风险控制

  • 监控请求成功率
  • 设置合理的并发限制
  • 实现优雅的错误恢复

结语

本文详细分析了Facebook爬虫的核心技术实现,涵盖了从请求构造到数据解析的完整流程。通过学习这些技术,我们可以更好地理解现代Web爬虫的设计原理和实现方法。

在实际应用中,请务必遵守相关法律法规,合理使用爬虫技术,尊重平台规则和用户隐私。技术的发展应该服务于正当的商业需求和学术研究,而不是恶意的数据窃取。


本文仅供技术学习参考,作者不承担因使用本文内容而产生的任何法律责任。

相关推荐
卷积殉铁子6 分钟前
SQL注入之联合查询注入
数据库
大模型铲屎官7 分钟前
【深度学习-Day 23】框架实战:模型训练与评估核心环节详解 (MNIST实战)
人工智能·pytorch·python·深度学习·大模型·llm·mnist
阿幸软件杂货间31 分钟前
PPT转图片拼贴工具 v1.0
python·powerpoint
AIGC_北苏44 分钟前
DrissionPage爬虫包实战分享
爬虫·python·drissionpage
小屁孩大帅-杨一凡1 小时前
在 Oracle 中,创建不同类型索引的 SQL 语法
数据库·sql·oracle
西柚小萌新1 小时前
【大模型:知识图谱】--5.neo4j数据库管理(cypher语法2)
数据库·知识图谱·neo4j
艾醒(AiXing-w)1 小时前
探索大语言模型(LLM):RSE流程详解——从文档中精准识别高相关片段
数据库·人工智能·语言模型
AI.NET 极客圈2 小时前
.NET 原生驾驭 AI 新基建实战系列(六):Pinecone ── 托管向量数据库的向量数据库的云原生先锋
数据库·人工智能·.net
华科云商xiao徐2 小时前
增量式网络爬虫通用模板
爬虫
YBCarry_段松啓2 小时前
uv:下一代 Python 包管理器
人工智能·python