由于软工项目选的是社交好友分析,又得捡起爬虫的那一套理论。。。社交好友分析应用数据才是王道,没有数据就没有发言权。写一个稳定的爬虫还是很重要的。准备爬github的时候发现github已经不再使用REST API,而是用的一个叫GraphQL API的东西。github上的教程在这,但是把这一套教程从头到尾看完花了我几个小时,于是想在这里写一个快速上手教程。
Hint:github GraphQL API 网页版测试工具 很好用,侧面还有文档,可以随时查,很方便
简单介绍GraphQL
现在使用大部分的API都是REST架构的,每一类询问都有一个终端连接,然后通过GET或者POST方式带参数查询就好了,这样的缺点是不同类型的数据(例如用户信息,动态信息,好友信息)就得分开多次查询,而且一次询问会得到许多我们不需要的数据,当我们只想得到用户名对应的用户id时同时会得到用户的其他信息。
而在GraphQL中总共只有一个终端 https://api.github.com/graphql,我们只需要对这个终端POST我们构造好的查询,就可以得到结构上一一对应的json返回数据,有很高的自由度,一个复杂的GraphQL查询有时可以等价于上千条传统REST API查询。(由于这个原因,github对GraphQL的访问频率限制比REST高一些)。
一个简单的Sample
query{ user(login:"Akaisorani"){ login name avatarUrl bioHTML } }
解释一下构造方式,QraphQL的操作分为Query和Mutation两种,一个是查询信息,另一个是修改或者发布信息。在爬虫中我们主要使用Query方式。
查询从user切入,由login(登录名,唯一的)切入,依次获得登录名,姓名,头像链接,简介。返回的json是这样的:
{ "data": { "user": { "login": "Akaisorani", "name": "Akaisora", "avatarUrl": "https://avatars0.githubusercontent.com/u/16610448?v=4", "bioHTML": "" } } }
一个复杂的Sample
query{ user(login: "Akaisorani") { repositories(first: 10,affiliations:[OWNER,COLLABORATOR,ORGANIZATION_MEMBER] ,orderBy: {field: PUSHED_AT, direction: DESC}) { edges { node { name owner{ login } pushedAt ref(qualifiedName: "master") { target { ... on Commit { id history(first: 10,author:{id:"MDQ6VXNlcjE2NjEwNDQ4"}) { pageInfo { hasNextPage } edges { node { committedDate messageHeadline oid message commitUrl author { name email } } cursor } } } } } } cursor } pageInfo{ hasNextPage } } } }
解释一下这个查询都干了啥。
首先从用户切入,获取该用户前10个repositories,角色可以是创建者,合作者以及组织成员,按照最后push的时间倒序给出(github要求这种询问必须要加first或者last数量)。接着是GraphQL中最核心的概念:
edges(边)和node(顶点)
repositories返回的结构并不是一个列表,而是一个边集,每条边指向网络中的一个顶点,repositories的边指向的就是一个个repository的node。通过这条边我们可以获得目标repo的名称,创建者,最后push时间等信息。GraphQL就是这样让我们可以把数据想象成一张网络,我们在网络上爬行,构造灵活巧妙的查询,一次性获得我们需要的一批信息。
我想获得一个repo的最近commits信息,发现在repo里怎么都找不到这个属性,后来网上查才知道是要通过ref来找到commit。
分页信息
上面的查询中可以看到edge有一个叫cursor的属性,每条边会有一个cursor属性,可以用来记录我们已经遍历到了哪一条边,在以后的询问中我们就可以用first:10,after:前一次的cursor,来访问接下来的10条记录。pageInfo属性是和edges并列的分页信息,其中的hasNextPage属性告诉我们是否存在下一页,可用作遍历时的终止条件。
query{ user(login: "Akaisorani") { repositories(first: 3,after:"Y3Vyc29yOnYyOpHOBTV7vw==") { edges { node { name owner { login } pushedAt } cursor } pageInfo { hasNextPage } totalCount } } }
得到的结果应该是
{ "data": { "user": { "repositories": { "edges": [ { "node": { "name": "PyMail", "owner": { "login": "Akaisorani" }, "pushedAt": "2017-07-10T14:18:16Z" }, "cursor": "Y3Vyc29yOnYyOpHOBcFw/A==" }, { "node": { "name": "blhx-auto-project", "owner": { "login": "Akaisorani" }, "pushedAt": "2017-09-04T05:41:57Z" }, "cursor": "Y3Vyc29yOnYyOpHOBhlOIw==" }, { "node": { "name": "SE-EX1-WordGraph", "owner": { "login": "Akaisorani" }, "pushedAt": "2017-11-27T15:04:09Z" }, "cursor": "Y3Vyc29yOnYyOpHOBiL0Zg==" } ], "pageInfo": { "hasNextPage": true }, "totalCount": 14 } } } }
使用变量
为了使查询可以更容易的复用,GraphQL还可以使用变量,观察一下下面的例子:
query ($login_name: String!, $author_id: ID!) { user(login: $login_name) { repositories(first: 2, affiliations: [OWNER, COLLABORATOR, ORGANIZATION_MEMBER], orderBy: {field: PUSHED_AT, direction: DESC}) { edges { node { name owner { login } pushedAt ref(qualifiedName: "master") { target { ... on Commit { id history(first: 2, author: {id: $author_id}) { pageInfo { hasNextPage } edges { node { committedDate messageHeadline oid message commitUrl author { name email } } cursor } } } } } } cursor } pageInfo { hasNextPage } } } }
variables:
{ "login_name": "Akaisorani", "author_id": "MDQ6VXNlcjE2NjEwNDQ4" }
这样我们就可以先构造查询模板,调用的时候改变参数就好了。
如何用Python实现
怎么用python实现也是个让新手(我)很头疼的事情,一个简明的Sample usage对于新手来说简直是雪中送炭。我经过一番暗黑摸索,搞出了如下套路:
query=""" query ($login_name: String!, $author_id: ID!) { user(login: $login_name) { repositories(first: 2, affiliations: [OWNER, COLLABORATOR, ORGANIZATION_MEMBER], orderBy: {field: PUSHED_AT, direction: DESC}) { edges { node { name owner { login } pushedAt ref(qualifiedName: "master") { target { ... on Commit { id history(first: 2, author: {id: $author_id}) { pageInfo { hasNextPage } edges { node { committedDate messageHeadline oid message commitUrl author { name email } } cursor } } } } } } cursor } pageInfo { hasNextPage } } } } """ url_graphql="https://api.github.com/graphql" session=requests.session() session.headers['Authorization']='bearer %s'%"你的Token" data={ "query":query, "variables": { "login_name": "Akaisorani", "author_id": "MDQ6VXNlcjE2NjEwNDQ4" } } r=session.post(url_graphql,data=json.dumps(data)) result=r.json() print(result)
requests包很好用,我们可以创建一个session保存会话,这样就不用每次都设定headers。headers里主要是添加Authorization为bearer方式的Token认证。data和普通post时候传的data不同,要多做一步json转化。
关于Token:Token是你要去自己的github上生成的,官方教程。记得不要把自己的Token给push到github上,不安全,而且会被github扫描出来给你revoke掉(不要问我是怎么知道的)。