1. Python

beijing2022-基于python3的api服务

最近北京2022冬奥会备受关注,不管是开幕式,还是运动员的比赛。看了几场比赛,感觉这些冰上运动根本不属于我的阶级。那就看看排行榜吧,那么,哪里的数据最权威呢?当然是北京2022冬奥会官网 :https://www.beijing2022.cn/en/ 。看了看实现方式,发现这个 Olympic Medal Table 的数据似乎是考虑了高并发,它并未采用api的方式获取,而是直接和页面一起返回的。看了一下响应头,如下图所示

果然选用了高并发服务器。先不管这么多,直接上 requets 吧,试了一下发现返回内容里面没有包含排行榜的table,估计又是页面的js在搞鬼,我也懒得看js代码了(其实是没找到),使用大招:requests-html 直接拿下。安装方式:

pip install requests-html

那么,现在已经有奖牌榜了,该提供api出去了,记得之前我写过一些使用python抓取数据然后使用php向外部提供api接口的,现在想一想,那样写限制太多了,不能施展出Python大法的威力。所以这次准备挑选一个Python的api框架,那就 FastAPI 吧。安装方式参考官网如下:

pip install fastapi[all]

根据官网的快速开始随便写了一下,接下了就是把上面写的爬虫和这个fastapi结合一下就ok了,可是事情的发展总是出乎意料。

当我第一次尝试结合的时候,发现requests-html 调用了chromium 且使用了 async/awiat ,但是fastapi的快速开始代码里面没有 async 的方法,我也不清楚能不能加,最后在官方文档中找到了:https://fastapi.tiangolo.com/zh/async/ 可算是解决了问题,那么windows测试运行是没有问题了。

当我把代码传到linux上运行后,又出现了问题,报错如下:

pyppeteer.errors.BrowserError: Browser closed unexpectedly

???最终通过 https://blog.csdn.net/u011054333/article/details/81055423  提供的解决方法成功解决,即对于 Pyppeteer 来说,python:3.7 内置的依赖库并不够,我们还需要额外进行安装。

apt-get update && \
  apt-get -y install libnss3 xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \
  libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \
  libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \
  libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \
  libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget && \

最终花费一个晚上加一个上午,终于可以运行了。

看一下官网的页面,对比一下:

目结构如下:

+pyapi
   - beijing2022.py
   - main.py

这样可以很方便扩展。

beijing2022.py

# beijing2022 冬奥会奖牌榜爬虫
# api.sencom.top:7000/beijing2022
# Author: BH6AOL
# Date: 2022-02-13

from bs4 import BeautifulSoup
from requests_html import AsyncHTMLSession


class OMT:
    """
    Olympic Medal Table
    """
    def __init__(self, order, noc, gold, silver, bronze, total, order_by_total):
        self.order = order
        self.noc = noc
        self.gold = gold
        self.silver = silver
        self.bronze = bronze
        self.total = total
        self.order_by_total = order_by_total


URL = "https://results.beijing2022.cn/beijing-2022/olympic-games/en/results/all-sports/medal-standings.htm"

cookies = {
    'acw_tc': '701ea19f16447610114827772e4bac216b906bcf729ef26bd42cf3d69e',
}

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Accept-Encoding': 'gzip, deflate, br',
    'Referer': 'https://results.beijing2022.cn/beijing-2022/olympic-games/en/results/all-sports/medalists.htm',
    'Connection': 'keep-alive',
    'Upgrade-Insecure-Requests': '1',
    'Sec-Fetch-Dest': 'document',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-Site': 'same-origin',
    'Sec-Fetch-User': '?1',
    'If-Modified-Since': 'Sun, 13 Feb 2022 12:24:10 GMT',
    'Cache-Control': 'max-age=0',
    'TE': 'trailers',
}


async def medal_standings():
    session = AsyncHTMLSession()
    response = await session.get(URL, headers=headers, cookies=cookies)
    await response.html.arender()
    soup = BeautifulSoup(response.html.html, 'html.parser')
    session.browser.close()  # 及时关闭浏览器,否则内存溢出有你好果汁吃!
    omt_list = []
    for tr in soup.tbody.find_all("tr"):
        tds = tr.find_all("td")
        order = tds[0].text.strip()
        noc = tds[1].text.strip()
        gold = tds[2].text.strip()
        silver = tds[3].text.strip()
        bronze = tds[4].text.strip()
        total = tds[5].text.strip()
        order_by_total = tds[6].text.strip()

        omt = OMT(order, noc, gold, silver, bronze, total, order_by_total)
        omt_list.append(omt)
    return omt_list


main.py

# api.sencom.top:8888
# 各种由Python 编写的接口合集
# Author: BH6AOL
# Date: 2022-02-13

import beijing2022
from fastapi import FastAPI


app = FastAPI()


@app.get("/")
def index():
    return "欢迎来到 BH6AOL 的个人 API 站点"


@app.get("/beijing2022")
async def get_beijing2022():
    try:
        medal_standings = await beijing2022.medal_standings()
        return {"status": "1", "data": medal_standings}
    except Exception as e:
        return {"status": "0", "data": e}


依赖:

  • python3
  • fastapi
  • bs4
  • requests-html

后台运行命令:

root@VM-16-3-debian:~/app/pyapi# uvicorn main:app --port 8888 > a.log 2>&1 &

可以使用nginx 将其代理出去,比如这样配置nginx.conf

server {
        listen       7000;
        server_name  localhost;

        location / {
            proxy_pass http://127.0.0.1:8888/;
        }
}

这样就可以通过外网访问了