获取twitter token 的方法

5,814 阅读3分钟

一. 介绍

不通过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-tokenauthorization, 而 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

1.jpg

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