Python开发Facebook相关服务时遇到的常见问题与解决方案

之前请教过大家如何用 flask,后来开发了个微信公众号的相关服务,这会儿玩 facebook,发现特别分裂。

背景描述:用户访问我的网站,然后通过 FB 登录,我的服务器拿到 token,通过 API 向 FB 服务器请求数据,处理后显示给用户。 需要事先注册好域名在 FB 开发者后台填写好,如果通过其他域名(比如 localtunnel 生成的链接)访问服务,会出错。

  • 开发姿势 1: 本地敲代码,然后 debug 模式运行 flask,然后把服务器 80 端口转发到本地,本地 flask 再爬个梯子连回服务器,向 FB 发送请求,可以绕地球七圈半……

  • 开发姿势 2: ssh 到服务器,开一个 vim 之类的编辑器,ssh 再开一个 session,运行程序;或者之前有人提到 pycharm 可以直接更新代码到远程服务器,不过之前试过那玩意儿太大了,觉得卡卡地……

两种姿势都觉得很分裂啊,大家有啥更优雅的姿势不?


Python开发Facebook相关服务时遇到的常见问题与解决方案

23 回复

首先,你可以用 tmux, 不需要搞那么多 ssh session.
其次,你可以在本地敲代码,然后 rsync 到服务器上,不一定要在服务器上开 ssh 敲。


Python开发Facebook相关服务常见问题与解决方案

搞Facebook开发,主要就是和它的两大API打交道:Graph APIMarketing API。下面是我踩过的一些坑和解决办法:

1. 访问令牌(Access Token)问题 这是最常见的问题。你的Token可能过期、权限不足或者压根儿就是错的。

  • 表现:收到400403错误,提示“Invalid OAuth access token”。
  • 解决
    • 短期用户令牌:用requests库通过OAuth流程换长期令牌。
    import requests
    
    APP_ID = '你的应用ID'
    APP_SECRET = '你的应用密钥'
    SHORT_LIVED_TOKEN = '你的短期令牌'
    
    url = f"https://graph.facebook.com/v18.0/oauth/access_token"
    params = {
        'grant_type': 'fb_exchange_token',
        'client_id': APP_ID,
        'client_secret': APP_SECRET,
        'fb_exchange_token': SHORT_LIVED_TOKEN
    }
    resp = requests.get(url, params=params)
    long_lived_token = resp.json().get('access_token') # 这就是长期用户令牌
    
    • 应用令牌:直接拼接,用于一些不需要用户上下文的操作。
    app_access_token = f"{APP_ID}|{APP_SECRET}"
    
    • 检查权限:去Facebook开发者后台的“应用面板” -> “权限和功能”里,确保你申请了接口需要的所有权限(例如pages_manage_posts, ads_management等)。

2. 分页数据获取不全 Graph API返回列表数据(如用户的相册、帖子的评论)默认有分页。

  • 解决:必须处理响应中的paging字段。
import requests

def get_all_posts(page_id, access_token):
    all_posts = []
    url = f"https://graph.facebook.com/v18.0/{page_id}/posts"
    params = {'access_token': access_token}

    while url:
        resp = requests.get(url, params=params)
        data = resp.json()
        all_posts.extend(data.get('data', []))
        # 关键:获取下一页的URL,如果没有则结束
        paging = data.get('paging')
        url = paging.get('next') if paging else None
        # 第一次请求后,后续的URL已经包含了参数,所以清空params
        params = {}

    return all_posts

3. 发布内容到Page(粉丝专页) 需要获取Page Access Token,并且你的用户对该Page有足够权限(如管理员)。

  • 步骤
    1. 获取用户的长期令牌(见问题1)。
    2. 用用户令牌获取其管理的Page列表和对应的Page Token。
    user_access_token = '你的长期用户令牌'
    url = f"https://graph.facebook.com/v18.0/me/accounts"
    params = {'access_token': user_access_token}
    resp = requests.get(url, params=params)
    pages = resp.json().get('data') # 列表,包含每个page的id, name, access_token
    
    1. 使用拿到的page_access_token发帖。
    page_id = '你的页面ID'
    page_access_token = pages[0]['access_token'] # 假设用第一个页面
    
    post_url = f"https://graph.facebook.com/v18.0/{page_id}/feed"
    payload = {
        'message': '这是来自API的测试帖子!',
        'access_token': page_access_token
    }
    resp = requests.post(post_url, data=payload)
    print(resp.json()) # 返回包含新帖子id的JSON
    

4. 处理速率限制(Rate Limiting) Facebook API有严格的调用频率限制。超限会返回80004错误码。

  • 解决
    • 在代码中主动添加延迟,尤其是循环调用时。用time.sleep()
    • 监控响应头中的X-App-UsageX-Business-Use-Case-Usage,接近100%时就要暂停。
    • 对非实时任务,使用异步队列(如Celery)来平滑请求。

5. Webhook验证和接收 设置Webhook时,Facebook会在你提交回调URL时发送一个GET请求进行验证。

  • 解决(使用Flask示例)
from flask import Flask, request, jsonify
import hashlib
import hmac

app = Flask(__name__)
APP_SECRET = '你的应用密钥' # 用于验证签名

@app.route('/webhook', methods=['GET', 'POST'])
def webhook():
    if request.method == 'GET':
        # 验证令牌
        mode = request.args.get('hub.mode')
        token = request.args.get('hub.verify_token')
        challenge = request.args.get('hub.challenge')
        # 你预先设置好的令牌
        my_verify_token = 'my_secure_token'

        if mode == 'subscribe' and token == my_verify_token:
            return challenge, 200
        else:
            return 'Verification failed', 403

    elif request.method == 'POST':
        # 接收实际数据
        data = request.get_json()
        # 可选:验证请求签名(X-Hub-Signature-256头),确保来自Facebook
        signature = request.headers.get('X-Hub-Signature-256', '')
        if not verify_signature(data, signature):
            return 'Invalid signature', 403

        # 处理数据,例如字段是'feed'代表主页动态
        if data.get('object') == 'page':
            for entry in data.get('entry', []):
                # ... 处理entry里的changes ...
                pass
        return 'OK', 200

def verify_signature(payload, signature):
    # 简化示例,实际需解析signature并与计算值比较
    expected_sig = hmac.new(APP_SECRET.encode('utf-8'), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest('sha256=' + expected_sig, signature)

总结建议 仔细读官方文档,用好Graph API Explorer工具测试,权限和Token管理是核心。

ngrok 转发到本地

ssh -fNR 转发到本地

搞一条能用的 facebook 的 host

VSCode 有个插件可以用 ftp 来同步代码,用了一段时间还好。

Sublime Text 的 FTPSync 插件不错
或者 RSync 一下?
或者干脆用 NFS 把远程服务器的目录挂载到本地?

试试改本地 host 记录,把域名指向本地

Mock 是正解

改 hosts 把域名指向本地即可

如果只是做 Facebook login 的话, 可以不使用 OAuth redirect 模式, 跳来跳去, 使用 SDK, 无需跳转…
在 PC 网页端 / 移动网页端 / App 端接入 SDK, 直接获取 fb token, 然后客户端拿着 fb token 来登录系统即可…
在 PC 网页端表现是一个弹出窗口让授权…

我水平比较菜,经常需要写一段之后 run 一下看看,然后发现有问题,有时候可能只改几个字母,再 run 一下……这样的话,在本地写好就会频繁需要 rsync,我试试 tmux 看看

谢谢!我看一下,之前用 localtunnel,类似的,但是每次都生成一个不同 url,因为要在 FB 开发者后台填上固定的,所以非常不方便

好方法,我试试看,如果 FB 和我的服务器之间交换数据时候不是直接访问我的域名的话,那应该就没问题!

lt --port 8080 --subdomain xxxxxx

用 localtunnel 生成一个固定的不就好了。

这种用 SDK 不跳转的方法获取的 token 是不是在用户端,然后由用户这边连接 FB 服务器去请求数据?我现在是按照官方文档给的那个 manually login 的方法,给用户一个链接,然后授权完后 redirect 回我的网站,带上参数,然后我这边就拿到 token,去 FB 抓取一些复杂的数据,然后处理之后返还给用户,处理过程比较复杂,不太合适在用户的浏览器里完成……

啊,原来是可以固定的啊,我没仔细看文档,谢谢了!

不过一般这种 OAuth,他们服务器都不直接访问你的域名,而是通过跳转来搞定的,所以本地 hosts 就够用了。甚至不本地 host 而是选择把某个子域名解析到 127.0.0.0.1 …(原理反正一样)

lt 固定域名的方式可以调微信公众号之类带 webhook 的…

手抖多打了个 0. 手机回复见谅

拿到 FB 的 token 了, 然后调用 FB 的接口, 客户端 /服务端都可以调用, 拿基本信息
我看代码即是这个接口

/**
* 根据前端 token 获取 Facebook 账户信息
/

const auth = exports.auth = co.wrap(function
(token) {
const me = yield rp.get({
json: true,
url: ‘https://graph.facebook.com/v2.8/me’,
qs: {
‘fields’: ‘id,name,email’,
‘access_token’: token,
}
})

// map userId
me.userId = me.id

// userId, name, email
return me
})

https://graph.facebook.com/v2.8/me 这个是公网接口, 前后端都可以调用, 如果你做的 FB 登录不需要让服务端知道, 也可以在客户端调用…但不让服务端知道没有什么意义…

http_proxy 环境变量

端口转发到本地啊

微信的不也是这样呀, 需要预先设置域名。

我当时的解决办法就是,修改 hosts,把域名解析到本地。开发完再删掉

回到顶部