You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在R中调用Twitter API v1.1更新推特个人主页横幅

R环境调用Twitter API v1.1更新主页横幅实现方案

需求说明

需要在R环境中通过Twitter API v1.1调用update_profile_banner接口更新个人推特主页横幅(头图),不限制实现方式:可以是现有R包提供的函数(目前已知主流R包暂不支持该功能),也可以通过system2("curl", args = args_strng)调用curl,或是其他任何可在R内运行的方案均可。


已尝试方案

方案1:基于rtweet(v0.7.0)实现

rtweet是对接Twitter API v1.1最主流的R包,内置大量接口函数,但暂不支持更新主页横幅的功能。
尝试基于rtweet自定义函数,传入符合尺寸要求(1500x500px)的本地图片路径,将图片编码后上传作为新的主页横幅,代码如下:

update_profile_banner <- function (banner_file, token = NULL) {

  token <- rtweet:::check_token(token)

  banner_uri <- base64enc::base64encode(banner_file)

  query <- "account/update_profile_banner"

  # 构建参数
  params <- list(banner = banner_uri)

  # 生成请求URL
  url <- rtweet:::make_url(query = query, param = params)

  # 发送请求
  r <- rtweet:::TWIT(get = FALSE, url, token)

  if (!r$status_code %in% c(200,201,202)) {
    return(httr::content(r))
  }

  message("主页横幅图片已更新!")
}

# 加载依赖,准备合法Twitter token
library(rtweet) # v.0.7.0
library(httr) # v.1.4.2

mytoken <- rtweet::create_token(
  app = "你的Twitter应用名称", 
  consumer_key = Sys.getenv("MY_CONSUMER_API_KEY"),
  consumer_secret = Sys.getenv("MY_CONSUMER_API_SECRET"),
  access_token = Sys.getenv("MY_ACCESS_TOKEN"),
  access_secret = Sys.getenv("MY_ACCESS_TOKEN_SECRET"),
  set_renv = FALSE
)

# 本地测试图片路径
test_png <- "本地符合尺寸要求的横幅图片路径"

update_profile_banner(test_png, mytoken)
#> Error in curl::curl_fetch_memory(url, handle = handle): HTTP/2 stream 0 was not closed cleanly: INTERNAL_ERROR (err 2)

初始报错参考价值有限,查询相关问题后将HTTP版本从2切换为1.1,得到更明确的报错:请求头过长。问题出在底层httr调用curl时,直接把超长的base64图片编码字符串拼接在URL参数里,没有区分请求头和请求体,导致请求头长度超限,请求形式如下:

POST https://api.twitter.com/1.1/account/update_profile_banner.json?banner=超长base64图片字符串+oauth鉴权信息

为验证该推测,用相同逻辑编写获取主页横幅的函数,传入短参数的场景下可以正常运行,确认问题就是POST请求携带的超长base64编码被错误放在URL参数中导致的。

get_profile_banner <- function (screen_name, token) {

  token <- rtweet:::check_token(token)

  query <- "users/profile_banner"

  # 构建参数
  params <- list(screen_name = screen_name)

  # 生成请求URL
  url <- rtweet:::make_url(query = query, param = params)
  r <- rtweet:::TWIT(get = TRUE, url, token)

    if (!r$status_code %in% c(200,201,202)) {
    return(httr::content(r))
  }

  rtweet:::from_js(r)
}

get_profile_banner("timteafan", mytoken)
# 返回结果正常,截断展示
#> $sizes
#> $sizes$ipad
#> $sizes$ipad$h
#> [1] 313
#> 
#> $sizes$ipad$w
#> [1] 626
#> 
#> $sizes$ipad$url
#> [1] "对应尺寸横幅的CDN地址"

方案2:通过system2("curl")直接调用curl

尝试的替代方案是通过R的system2()直接调用curl,构造规范请求,分离携带授权信息的请求头和携带图片字符串的请求体。
该方案的难点是需要自行生成OAuth签名,自行实现失败后,改为通过httr生成签名和全部OAuth凭证,再作为请求头传入curl命令。
首先测试获取主页横幅的逻辑,代码如下,可以正常运行:

get_banner <- function(screen_name, token) {

  query <- "users/profile_banner"

  url <- rtweet:::make_url(query = query,
                           param = list(screen_name = screen_name))

  # 生成oauth凭证
  oauth <- oauth_GET(url, token)
  oauth <- oauth[[which(names(oauth) == "Authorization")]]

  # 构造curl命令
  req <- "--get 'https://api.twitter.com/1.1/users/profile_banner.json?'"
  data <- paste0("--data 'screen_name=", screen_name, "'")
  header <- paste0("--header 'Authorization:", oauth, "'")

  args <- paste(req, data, header, collapse = " ")
  system2("curl", args = args)
}

# 辅助函数
oauth_GET <- function (url = NULL, config = list(), ..., handle = NULL) {
  hu <- httr:::handle_url(handle, url, ...)
  req <- httr:::request_build("GET", hu$url, as.token.request(config), ...) 
  oauth_req(req, hu$handle$handle)
}

oauth_req <- function (req, handle) {
  stopifnot(httr:::is.request(req), inherits(handle, "curl_handle"))
  req <- httr:::request_prepare(req)
  req$headers
}

as.token.request <- function(x) auth_req(auth_token = x)
auth_req <- function (auth_token) {
  structure(list(auth_token = auth_token), class = "request")
}

library(httr)
get_banner("timteafan", mytoken)
# 正常返回横幅信息,结果略

用相同思路编写上传横幅的函数后运行失败,返回code32“无法完成认证”的错误,原因是生成授权信息时,超长图片字符串提前拼入URL参与签名,和最终curl发送的请求结构不匹配,导致签名校验失败。

upload_banner <- function(banner_file, token) {
  query <- "account/update_profile_banner"

  banner_uri <- base64enc::base64encode(banner_file)

  url <- rtweet:::make_url(query = query,
                           param = list(banner = banner_uri))

  oauth <- oauth_POST(url, token)
  oauth <- oauth[names(oauth) == "Authorization"]

  req <- "--request POST 'https://api.twitter.com/1.1/account/update_profile_banner.json?'"

  header <- paste0("--header 'Authorization: ", oauth, "'")
  header2 <- paste0("--header 'Content-Type: application/json'")
  header3 <- paste0("--header 'Accept: application/json, text/xml, application/xml, */*'")
  data <- paste0('--data "banner=', banner_uri,'"')

  myargs <- paste(req, header2, header3, header, data, collapse = " ")
  system2("curl", args = myargs)
}

oauth_POST <- function (url = NULL, config = list(), ...,
                        body = NULL, encode = c("multipart", "form", "json", "raw"),
                        handle = NULL) {

  encode <- match.arg(encode)
  hu <- httr:::handle_url(handle, url, ...)
  req <- httr:::request_build("POST", hu$url, httr:::body_config(body, match.arg(encode)),
                      auth_req(config), ...)

  oauth_req(req, hu$handle$handle)
}

upload_banner(test_png, mytoken)
#> {"errors":[{"message":"Could not authenticate you","code":32}]}

尚未尝试的方向

  • rtweet开发版:从公开的源码来看,底层对httr的调用逻辑没有大幅改动,预计问题仍然存在。
  • httr2包:此前看到资料提及该包不支持OAUTH 1.0认证,暂未尝试。
  • 其他R包方案:如直接使用curl、RCurl或其他可对接Twitter API的R包。

接口可用性验证

目前API v1.1的该接口仍可正常使用,通过Python的tweepy包可成功调用更新横幅,参考代码如下:

import os
import tweepy

# 读取环境变量中的密钥
CONKEY = os.getenv('MYTWITTER_CONSUMER_API_KEY')
CONSEC = os.environ.get('MYTWITTER_CONSUMER_API_SECRET')
ACCKEY = os.getenv('MYTWITTER_ACCESS_TOKEN')
ACCSEC = os.environ.get('MYTWITTER_ACCESS_TOKEN_SECRET')

auth = tweepy.OAuth1UserHandler(
   CONKEY,
   CONSEC,
   ACCKEY,
   ACCSEC
)

api = tweepy.API(auth)

image_url = '本地横幅图片路径/可访问的图片直链'

api.update_profile_banner(image_url)

可行解决方案

问题核心有两个:一是OAuth 1.0签名时不能把base64编码的图片参数拼到URL里参与签名,二是图片数据要放在POST请求体中,不能放在URL参数里。直接用httr的POST函数,把banner参数放在body里用form编码即可,不需要绕弯调用系统curl,代码如下:

update_twitter_banner <- function(banner_path, token = NULL) {
  # 检查token合法性
  token <- rtweet:::check_token(token)
  
  # 读取本地图片转base64,如果是网络图片先下载到本地临时文件
  if (grepl("^https?://", banner_path)) {
    tmp <- tempfile(fileext = ".png")
    on.exit(unlink(tmp))
    download.file(banner_path, tmp, mode = "wb", quiet = TRUE)
    banner_path <- tmp
  }
  banner_b64 <- base64enc::base64encode(banner_path)
  
  # 基础接口地址,不带任何参数
  base_url <- "https://api.twitter.com/1.1/account/update_profile_banner.json"
  
  # 发送POST请求,banner参数放在form格式的请求体中
  resp <- httr::POST(
    url = base_url,
    config = token,
    body = list(banner = banner_b64),
    encode = "form"
  )
  
  # 处理返回结果
  if (httr::status_code(resp) %in% c(200, 201, 202)) {
    message("主页横幅更新成功!")
    invisible(TRUE)
  } else {
    err_info <- httr::content(resp, as = "parsed")$errors[[1]]
    stop(paste0("更新失败,错误码", err_info$code, ":", err_info$message))
  }
}

# 调用示例
update_twitter_banner(test_png, mytoken)

注意:传入的图片必须严格符合1500x500px的尺寸要求,否则接口会返回参数错误。


内容的提问来源于stack exchange,提问作者TimTeaFan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 06:30:38