项目源码
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 : 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' : 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' : for i in range (3 ): info = readexcelbyid(str1, i) if info[0 ] != 0 : break else : for i in range (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 = r'' driver = webdriver.Chrome() driver.get(url) time.sleep(50 ) with open ('pintia_cookies.txt' , 'w+' ) as f: 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' ) 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) 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)) data = browser.find_element("class name" , 'DataTable_1vh8W' ).text score = scoreRe.findall(data) page += 1 browser.quit() return score
这里有几个注意点:
传入参数为 要爬取的网页(url)和 要匹配的学生学号(stuid)。
加Cookie前要先get一遍网页,不然不知道Cookie加哪。
data = browser.find_element("class name", 'DataTable_1vh8W').text
的意思是找一个class名称为’DataTable_1vh8W’(需自行调试)的元素,返回它的文本并保存到data里,以便后续正则分析。
正则表达式视具体情况而定,需自行调试。
最后的循环实现了逐页查找的功能。这个功能的实现有两个前提。一是获取到网页的最大页数(maxpage),二是通过拼接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无此记录' )
写在最后
挺烂的活。没写过静态爬虫就来写动态了,有点吃力。
之前学的正则有点忘了,复习了一下午。