项目源码

https://github.com/CH3COOH12138/PTASpider

项目结构:

文件名 作用
main.py 主程序
pintia_cookies.txt Cookie
requirement.txt 依赖
studata.xlsx 信息映射
urls.txt 爬取的网址
readme.md 注意事项

前情提要

某计导老师很喜欢按学期布置作业,66道布置完了又来98道,外加机考等不定期测试,有些卷✌️次次AK(请勿代入)。我们可以通过这项数据推测一个同学的计导学习进度以及是不是✌️。
但是PTA的排名界面只显示学号,记住整个院的学号和姓名的对应关系未免太费脑细胞,我们可以通过Python链接excel表格查询班级、学号、姓名等信息。
查完了顺便在PTA里读一下成分(无恶意)。

预备工作

安装依赖

1
pip install -r requirement.txt

安装 Selenium WebDriver

静态网页的爬取用requests库即可,然而PTA的排名页是动态的,数据不在源码中。
对于动态网页的爬取,这里采用的是Selenium测试工具。它支持所有主流的浏览器(包括 IE、Firefox、Safari、Opera和Chrome等),可以使用它对浏览器进行各种各样的模拟操作,包括爬取一些网页内容。
不过,Selenium自己不带浏览器,它需要与第三方浏览器结合在一起才能使用。要使用它,需要下载对应浏览器的WebDriver并放到Python的Scripts文件夹下。
Selenium基础入门:https://blog.csdn.net/benzhujie1245com/article/details/117089767

读取数据映射

这里用到了pandas库来读取excel文件
项目文件的excel共有三张工作表,可以使用file = pd.read_excel(path, sheet_name=sheets)读取。sheets参数可以是字符串(工作表名称),数字(默认从0开始编号)或列表,但是如果传入列表的话传出也是列表,干脆用循环一张一张表读。

这里为了实现学号和姓名的双重关键字查找写了俩函数(每张工作表的最后一行用0填充来代表结尾):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def readexcelbyid(stuid, sheets):
info = [0 for _ in range(5)]
file = pd.read_excel(path, sheet_name=sheets)
index = 0
data = file.iloc[index, 1]
while data != 0: # 表的最后一行用0填充,代表结尾
if str(data) == stuid: # 找到
for i in range(5):
info[i] = file.iloc[index, i]
break
index += 1
data = file.iloc[index, 1]
return info


def readexcelbyname(name, sheets):
info = [0 for _ in range(5)]
file = pd.read_excel(path, sheet_name=sheets)
index = 0
data = file.iloc[index, 2]
while data != '0': # 表的最后一行用0填充,代表结尾
if data == name: # 找到
for i in range(5):
info[i] = file.iloc[index, i]
break
index += 1
data = file.iloc[index, 2]
return info


if __name__ == "__main__":

str1 = input("请输入学号或姓名:\n")
if str1[0:3] == '202': # 输入前3位是'202'代表学号
for i in range(3): # 读3张表
info = readexcelbyid(str1, i)
if info[0] != 0:
break
else:
for i in range(3): # 读3张表
info = readexcelbyname(str1, i)
if info[0] != 0:
break

if info[0] != 0: # 找到
print(info)
else:
print("查无此人")

input()

爬取PTA动态网页

获取Cookie

第一次爬取之前需要手动登录账户以保存Cookie。

1
2
3
4
5
6
7
8
9
10
11
12
def getcookie():
# 填写完整url
url = r''
driver = webdriver.Chrome()
driver.get(url)
# 程序打开网页后50秒内 “手动登陆账户”
time.sleep(50)
with open('pintia_cookies.txt', 'w+') as f:
# 将cookies保存为json格式
f.write(json.dumps(driver.get_cookies()))
driver.close()

爬取网页

弄好Cookie后我们就可以欢乐地爬了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def ptaspider(url, stuid):
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless') # 静默运行
chrome_options.add_argument('--disable-gpu') # 禁用gpu渲染
chrome_options.add_argument('--incognito') # 无痕模式
chrome_options.add_experimental_option('excludeSwitches', ['enable-logging']) # 禁止打印日志
browser = webdriver.Chrome(options=chrome_options)
browser.implicitly_wait(10) # 隐式等待
browser.get(url)
with open(cookiepath, 'r') as f:
cookies_list = json.load(f)
for cookie in cookies_list:
browser.add_cookie(cookie) # 加Cookie
browser.get(url)
data = browser.find_element("class name", 'DataTable_1vh8W').text
scoreRe = re.compile('([0-9]+) ' + str(stuid) + ' ([0-9]+)')
score = scoreRe.findall(data)
page = 1
if len(score) == 0:
maxpage = browser.find_elements("class name", 'pageItem_3P4fJ')[-2].text
while len(score) == 0 and page < int(maxpage):
browser.get(url + '?page=' + str(page)) # 拼接url
data = browser.find_element("class name", 'DataTable_1vh8W').text
score = scoreRe.findall(data)
page += 1
browser.quit()
return score

这里有几个注意点:

  1. 传入参数为 要爬取的网页(url)和 要匹配的学生学号(stuid)。
  2. 加Cookie前要先get一遍网页,不然不知道Cookie加哪。
  3. data = browser.find_element("class name", 'DataTable_1vh8W').text的意思是找一个class名称为’DataTable_1vh8W’(需自行调试)的元素,返回它的文本并保存到data里,以便后续正则分析。
  4. 正则表达式视具体情况而定,需自行调试。
  5. 最后的循环实现了逐页查找的功能。这个功能的实现有两个前提。一是获取到网页的最大页数(maxpage),二是通过拼接url得出每页的访问网址。
  6. 最后返回的是一个列表,代表每个查找到的位置的对应正则匹配(单处匹配返回文本,多处匹配返回元组)。

通过正则表达式获取url列表

我们只剩最后一步了:读取项目目录下的urls.txt文件并获取url列表。

默认的urls.txt写入文件的格式应为:

1
example:https://www.example.com

显然,我们可以通过'(.+?):(.+)'分别对每条数据的名称和网址进行匹配。需要注意的是https://里也有:,对名称的匹配应使用非贪婪模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
urlRe = re.compile(r'(.+?):(.+)')
names = []
urls = []
with open(urlpath) as f:
str2 = f.readlines()
for i in range(len(str2)):
names.append(urlRe.findall(str2[i])[0][0])
urls.append(urlRe.findall(str2[i])[0][1])
for i in range(len(urls)):
score = ptaspider(urls[i], info[1])
if len(score) != 0:
print('\n' + names[i] + '\n排名:' + score[0][0] + '\n分数:' + score[0][1])
else:
print('\n' + names[i] + '\n无此记录')

写在最后

挺烂的活。没写过静态爬虫就来写动态了,有点吃力。
之前学的正则有点忘了,复习了一下午。