由于软工项目选的是社交好友分析,又得捡起爬虫的那一套理论。。。社交好友分析应用数据才是王道,没有数据就没有发言权。写一个稳定的爬虫还是很重要的。准备爬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掉(不要问我是怎么知道的)。

