一. 介绍
不通过twitter的开放api访问twitter数据的时候,就算不登录,也需要在每次请求的headers中带上guest信息,且guest的关键信息通过服务端生成,会通过这些信息来对数据访问频次进行限制。
因为使用twitter的api来获取一些数据本身就会对调用速率和调用最大值有限制,很多时候我们需要获取更多数据,这种情况下就必然要想到twitter的网站本身数据,二获取twitter网站的数据,就绕不开这个 guest token
二. token的获取
获取这个guest token 目前用过两种方式,一种是以模拟浏览器作为一个服务,专门用于生成token,开放一个接口给应用来调用,另外一种方式是请求twitter网页,在网页中获取到这个参数
1. 模拟浏览器
a. 说明
模拟浏览器的方式需要尽量隐藏无头浏览器的标识,众所周知,selenium + chromedriver的标识只要对方想甄别就会知道你是正常浏览器还是无头浏览器,所以这里我用了 google新出的 puppeteer,其实还是驱动的chromium,虽然还是有标识可以识别,但比起chromedriver来说性能和隐蔽性都要好一些
使用这种方式不用去网页元素里找token信息,而是通过拦截浏览器请求的request,从里边获取到我们要的东西
主要的参数是x-guest-token 和 authorization, 而 authorization实际上可以不用变
b. 代码(python版)
import asyncio
import json
import aiohttp
from pyppeteer import launch
# 用于去掉浏览器标识的一个工具
from pyppeteer_stealth import stealth
async def get_guest_token():
url = 'https://twitter.com/RFA_Chinese'
browser = await launch({
'headless': True,
'ignoreHTTPSErrors': False,
'args': [
'--disable-extensions',
'--hide-scrollbars',
'--disable-bundled-ppapi-flash',
'--mute-audio',
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-gpu',
'--ignore-certificate-errors',
'--ignore-certificate-errors-spki-list',
'--proxy-server=http://proxy_host:proxy_port'
],
})
# 通过拦截request的方式获取token
async def handle_request(request):
resource_type = request.resourceType
if resource_type in ['xhr', 'fetch']:
if request.url.find('https://api.twitter.com/2/timeline/profile/998336069992001537.json') == -1:
headers_dict = request.headers
if headers_dict and 'authorization' in headers_dict.keys() and 'x-guest-token' in headers_dict.keys():
cookies = dict()
_authorization = headers_dict.get('authorization')
cookies['authorization'] = _authorization
_x_guest_token = headers_dict.get('x-guest-token')
cookies['x-guest-token'] = _x_guest_token
if _x_guest_token and _authorization:
cookies_str = json.dumps(cookies, ensure_ascii=False)
r_conn.set('twitter_guest_token', cookies_str)
logger.info('get new twitter guest token:%s' % cookies_str)
return await request.continue_()
page = await browser.newPage()
# 隐藏浏览器特征
await stealth(page)
await page.setRequestInterception(True)
page.on('request', lambda req: asyncio.ensure_future(handle_request(req)))
await page.goto(url)
await asyncio.sleep(1)
await browser.close()
if __name__=='__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(get_guest_token())
2. 从网页中获取
a. 说明
用无痕浏览器,请求一个twitter网页,比如twitter.com/ , 在页面查找可以找到authorization参数,叫做gt=xxxx
b. 代码(python)
# -*- coding: utf-8 -*-
import re
import time
import requests
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
class TokenExpiryException(Exception):
def __init__(self, msg):
super().__init__(msg)
class RefreshTokenException(Exception):
def __init__(self, msg):
super().__init__(msg)
class Token:
def __init__(self):
self._session = requests.Session()
self._session.headers.update(
{'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0'})
self.Guest_token = None
self._retries = 5
self._timeout = 10
self.url = 'https://twitter.com'
def _request(self):
for attempt in range(self._retries + 1):
# The request is newly prepared on each retry because of potential cookie updates.
req = self._session.prepare_request(requests.Request('GET', self.url))
logger.debug(f'Retrieving {req.url}')
try:
r = self._session.send(req, allow_redirects=True, timeout=self._timeout)
except requests.exceptions.RequestException as exc:
if attempt < self._retries:
retrying = ', retrying'
else:
retrying = ''
logger.error(f'Error retrieving {req.url}: {exc!r}{retrying}')
else:
success, msg = (True, None)
msg = f': {msg}' if msg else ''
if success:
logger.debug(f'{req.url} retrieved successfully{msg}')
return r
if attempt < self._retries:
sleep_time = 2.0 * 2 ** attempt
logger.info(f'Waiting {sleep_time:.0f} seconds')
time.sleep(sleep_time)
else:
msg = f'{self._retries + 1} requests to {self.url} failed, giving up.'
logger.fatal(msg)
self.Guest_token = None
raise RefreshTokenException(msg)
def refresh(self):
logger.debug('Retrieving guest token')
res = self._request()
match = re.search(r'\("gt=(\d+);', res.text)
if match:
logger.debug('Found guest token in HTML')
self.Guest_token = str(match.group(1))
else:
self.Guest_token = None
raise RefreshTokenException('Could not find the Guest token in HTML')
if __name__=='__main__':
t = Token()
t.refresh()
三. 总结
1. 判断token是否过期
通过网页请求最终得到,token过期后(这个过期有可能是真的过期了,有可能是请求频率太高被twitter禁用了),返回的状态码是429,所以如果业务请求的时候返回的是429,那基本断定是token需要更换了,直接调用refresh 就可切换一个新的guest token
2. 比较
两种方式无疑第一种比较耗费资源且性能也不佳,最好是使用第二种方式,而且可以把 authorization参数固定,比如第二种方式我就用了一个固定的authorization,每次只用变换 x-guest-token 即可
3. 使用
每次请求在headers里带上guest token 关键信息
def get():
url = ''
proxies = {xxx}
headers = {
'x-guest-token': Guest_token,
'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA'
}
resp = requests.get(url=url, headers=headers, proxies=proxies)
if resp.status_code == 429:
raise TokenExpiryException(json.loads(resp.text)['errors'][0]['message'])
return resp