01月15, 2019

校 OJ 爬虫

最近被网课整得精疲力竭,写个爬虫消遣一下。

OJ Rank


偶然的发现

校 OJ 有分享代码的功能,但是只能查看图片。

偶然发现,在电脑上图片里的代码显示完全,而手机图片里的代码竟然没显示全。

电脑上的代码图:

code picture on PC

手机上的代码图:

code picture on phone

很明显图片是前端 JS 生成的,肯定有服务端向客户端运输代码的这么一个过程。这个过程可能存在加密,但是通过分析 JS 也能得出解密的方法。

通过抓包,发现中途产生了这么一个 json:

catch

这是别人分享的代码,没有加密,就是一个简单的 HTML 编码。

于是可以写个爬虫把别人分享的代码都提交了。


爬取 status 信息

这个请求包里,runid决定了需要查看的代码。在校 OJ 上,每一次成功的提交都会被授予一个runid,那么首先的任务就是爬取所有的 status。但是这个爬取操作比较简单,我可以不用写代码。

因为在查看 status 的时候,产生了这么一个请求:

status

通过修改参数iDisplayLength的值,即可控制单页显示的信息条数,所以我可以用一个请求获得所有的 status 信息。

但是当iDisplayLength的值超过了数据库中仅有数据的条数时,会产生500错误,如图:

wrong 500

故使用二分法,得知总共有95687条结果为 AC 的提交记录,全都存到本地的文件里。


本地筛选

其中的一条数据如下:

["1004176124","335327","2016","Accepted","GNU C++","2 ms","632 KB","411 B","2019-01-13 23:55:11",1]

末尾的1表示这次提交选择了分享代码,我只需要选择了分享代码的提交号。

而且对于每一道题,我只需要一个 AC 的代码就够了,于是闲的蛋疼写了个 cpp 进行筛选:

#include <fstream>
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
    map<string,int> mmp;
    fstream myfile,outfile;
    myfile.open("timu.txt",ios::in);
    outfile.open("res.txt",ios::out);
    string nows="",lasts="",id="";
    int flag=0,cnt=0;
    char c;
    while(!myfile.eof())
    {
        myfile>>c;
        if(flag==1&&c!=']')
        {
            nows+=c;
            if(c=='\"')
                cnt++;
            else if(cnt==3)
                lasts+=c;
            else if(cnt==5)
                id+=c;
        }
        if(!flag&&c=='[')
            flag=1;
        if(flag&&c==']')
        {
            if(nows[nows.length()-1]=='1' && !mmp[id])
            {
                outfile<<lasts<<' '<<id<<"\n";
                mmp[id]=1;
            }
            nows="";
            lasts="";
            id="";
            flag=0;
            cnt=0;
        }
    }
    myfile.close();
    outfile.close();
    return 0;
}

筛出了每个题所需的runid

runid


requests

懒得解释了,上 py:

import requests
import json
import html
import time
headers={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:64.0) Gecko/20100101 Firefox/64.0'}
cookies={'cugboj_v3_username':'1004176124','cugboj_v3_password':'不给你看哦~~'}
url_scrach="http://acm.cugb.edu.cn/ajax/get_source.php?runid="
url_submit="http://acm.cugb.edu.cn/ajax/problem_submit.php"
submit_data={
    'user_id':'1004176124',
    'problem_id':'',
    'language':'1',
    'isshare':'0',
    'source':'',
    'login':'Submit'
}

file=open(r"./res.txt","r")
runids=[]
pids=[]
line=file.readline()
while line:
    temp=line.split(' ')
    runids.append(temp[0])
    pids.append(temp[1][:-1])
    line=file.readline()
file.close()
print("finish reading")

for i in range(len(pids)):
    code_res=requests.post(url_scrach+runids[i],cookies=cookies,headers=headers)
    code=html.unescape(json.loads(code_res.text)['source'])[24:]
    submit_data['problem_id']=pids[i]
    submit_data['source']=code
    submit_res=requests.post(url_submit,cookies=cookies,headers=headers,data=submit_data)
    print(pids[i]+submit_res.text)
    time.sleep(5)

最后的time.sleep(5)是为了防止 OJ 处理不过来,故五秒交一发。

效果:

run

run status


后话

写这个爬虫的目的还是练练手,如果有必要,请学长把我的排名 ban 了,谢谢。

本文链接:https://blog.cindemor.com/post/oj-spider.html

-- EOF --