动态网页爬虫Python实现记录-如何在不登录的情况下获取LeetCode-CN用户提交记录

之前偶然看到有位博主弄了一个打卡记录网站:地址,用户只要提交在LC上的用户名就可以自动爬取做题记录。我只弄过一个登录版的爬虫,在知道用户名和密码的情况下登录后可爬取提交代码。所以看到这个网站之后,我蠢蠢欲动,也想弄一个不需要登录的提交结果查询爬虫,以后可以联动微信群机器人实现全自动化统计打卡群成员每天的打卡结果。

 

首先去上述博主的博客和github逛了一圈,发现项目没有开源,所以只能先试试手动造轮子。

接着在未登录的情况下,随机打开一名用户的LC中国主页,可以看到在网页的中部和下部就是该用户的历史提交记录。

动态网页爬虫Python实现记录-如何在不登录的情况下获取LeetCode-CN用户提交记录_第1张图片

接着打开网页审查元素,把鼠标放在希望爬取的内容上面。

动态网页爬虫Python实现记录-如何在不登录的情况下获取LeetCode-CN用户提交记录_第2张图片

OK成了,接下来似乎只要把html文件爬取下来即可,于是参考某教程(教程链接找不到了真不好意思)得到以下代码:

import requests, urllib
from bs4 import BeautifulSoup
import json

url = 'https://leetcode-cn.com/u/awayfuture-tfjify50il/'
def open_url(url):
    head = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/5'
                          '37.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
    req = urllib.request.Request(url, headers=head)
    response = urllib.request.urlopen(req)

    html = response.read()
    html_str = html.decode('utf-8')
    print (html_str)


if __name__ == '__main__':
    open_url(url)

 看一看爬取下来的结果:结果。

然后突然发现:咦怎么没有我之前看到的东西?

学习一番之后明白了:这是一个动态加载的网站,而我使用的是静态的爬取方法,真正的内容都是由js动态加载得到。

emmmm,行吧我再学学怎么爬动态网站。参考教程:教程。

接着按照教程的指示,把审查元素调整到 Network 选项卡,然后刷新网站,观察此网站的通讯情况,如下图所示:

动态网页爬虫Python实现记录-如何在不登录的情况下获取LeetCode-CN用户提交记录_第3张图片

下一步就很无聊了,把每个后缀不是js也不是css的文件看一遍,试图寻找数据到底是从哪里传输的。

一番搜索之后终于找到了我想要的数据,如下图所示。

动态网页爬虫Python实现记录-如何在不登录的情况下获取LeetCode-CN用户提交记录_第4张图片

继续按照教程指示,根据 Preview 选项卡里的 Payload,模拟 POST 行为来得到交互链接。

动态网页爬虫Python实现记录-如何在不登录的情况下获取LeetCode-CN用户提交记录_第5张图片

所以能得到如下链接:

https://leetcode-cn.com/graphql?oprationName=recentSubmissions&variables={%22userSlug%22:%22awayfuture-tfjify50il%22}&query=query%20recentSubmissions($userSlug:%20String!){recentSubmissions(userSlug:%20$userSlug){status%20lang%20question{questionFrontendId%20title%20translatedTitle%20titleSlug%20__typename}submitTime%20__typename}}

访问这串链接试一试,恰好就是我想要的数据:结果。

小改一下代码:

import requests, urllib
from bs4 import BeautifulSoup
import json

USERNAME = "awayfuture-tfjify50il"
url = "https://leetcode-cn.com/graphql?oprationName=recentSubmissions&variables={%22userSlug%22:%22" + USERNAME + "%22}&query=query%20recentSubmissions($userSlug:%20String!){recentSubmissions(userSlug:%20$userSlug){status%20lang%20question{questionFrontendId%20title%20translatedTitle%20titleSlug%20__typename}submitTime%20__typename}}"

def open_url(url):
    head = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/5'
                          '37.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
    req = urllib.request.Request(url, headers=head)
    response = urllib.request.urlopen(req)

    html = response.read()
    html_str = html.decode('utf-8')
    t = json.loads(html_str)

    for item in t['data']['recentSubmissions']:
        print (item)


if __name__ == '__main__':
    open_url(url)

即可得到稍微处理之后的数据:

{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '739', 'title': 'Daily Temperatures', 'translatedTitle': '每日温度', 'titleSlug': 'daily-temperatures', '__typename': 'QuestionNode'}, 'submitTime': 1570504172, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '141', 'title': 'Linked List Cycle', 'translatedTitle': '环形链表', 'titleSlug': 'linked-list-cycle', '__typename': 'QuestionNode'}, 'submitTime': 1570501637, '__typename': 'FeedSubmissionNode'}
{'status': 'A_15', 'lang': 'A_1', 'question': {'questionFrontendId': '141', 'title': 'Linked List Cycle', 'translatedTitle': '环形链表', 'titleSlug': 'linked-list-cycle', '__typename': 'QuestionNode'}, 'submitTime': 1570501479, '__typename': 'FeedSubmissionNode'}
{'status': 'A_11', 'lang': 'A_1', 'question': {'questionFrontendId': '141', 'title': 'Linked List Cycle', 'translatedTitle': '环形链表', 'titleSlug': 'linked-list-cycle', '__typename': 'QuestionNode'}, 'submitTime': 1570501386, '__typename': 'FeedSubmissionNode'}
{'status': 'A_20', 'lang': 'A_1', 'question': {'questionFrontendId': '141', 'title': 'Linked List Cycle', 'translatedTitle': '环形链表', 'titleSlug': 'linked-list-cycle', '__typename': 'QuestionNode'}, 'submitTime': 1570501357, '__typename': 'FeedSubmissionNode'}
{'status': 'A_11', 'lang': 'A_1', 'question': {'questionFrontendId': '141', 'title': 'Linked List Cycle', 'translatedTitle': '环形链表', 'titleSlug': 'linked-list-cycle', '__typename': 'QuestionNode'}, 'submitTime': 1570501039, '__typename': 'FeedSubmissionNode'}
{'status': 'A_11', 'lang': 'A_1', 'question': {'questionFrontendId': '141', 'title': 'Linked List Cycle', 'translatedTitle': '环形链表', 'titleSlug': 'linked-list-cycle', '__typename': 'QuestionNode'}, 'submitTime': 1570500878, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '20', 'title': 'Valid Parentheses', 'translatedTitle': '有效的括号', 'titleSlug': 'valid-parentheses', '__typename': 'QuestionNode'}, 'submitTime': 1542809216, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '844', 'title': 'Backspace String Compare', 'translatedTitle': '比较含退格的字符串', 'titleSlug': 'backspace-string-compare', '__typename': 'QuestionNode'}, 'submitTime': 1542717452, '__typename': 'FeedSubmissionNode'}
{'status': 'A_15', 'lang': 'A_1', 'question': {'questionFrontendId': '844', 'title': 'Backspace String Compare', 'translatedTitle': '比较含退格的字符串', 'titleSlug': 'backspace-string-compare', '__typename': 'QuestionNode'}, 'submitTime': 1542717397, '__typename': 'FeedSubmissionNode'}
{'status': 'A_15', 'lang': 'A_1', 'question': {'questionFrontendId': '844', 'title': 'Backspace String Compare', 'translatedTitle': '比较含退格的字符串', 'titleSlug': 'backspace-string-compare', '__typename': 'QuestionNode'}, 'submitTime': 1542717304, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '155', 'title': 'Min Stack', 'translatedTitle': '最小栈', 'titleSlug': 'min-stack', '__typename': 'QuestionNode'}, 'submitTime': 1542704826, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '225', 'title': 'Implement Stack using Queues', 'translatedTitle': '用队列实现栈', 'titleSlug': 'implement-stack-using-queues', '__typename': 'QuestionNode'}, 'submitTime': 1542699926, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '232', 'title': 'Implement Queue using Stacks', 'translatedTitle': '用栈实现队列', 'titleSlug': 'implement-queue-using-stacks', '__typename': 'QuestionNode'}, 'submitTime': 1542509499, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '682', 'title': 'Baseball Game', 'translatedTitle': '棒球比赛', 'titleSlug': 'baseball-game', '__typename': 'QuestionNode'}, 'submitTime': 1542379983, '__typename': 'FeedSubmissionNode'}
{'status': 'A_20', 'lang': 'A_1', 'question': {'questionFrontendId': '682', 'title': 'Baseball Game', 'translatedTitle': '棒球比赛', 'titleSlug': 'baseball-game', '__typename': 'QuestionNode'}, 'submitTime': 1542377590, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '496', 'title': 'Next Greater Element I', 'translatedTitle': '下一个更大元素 I', 'titleSlug': 'next-greater-element-i', '__typename': 'QuestionNode'}, 'submitTime': 1542026974, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '557', 'title': 'Reverse Words in a String III', 'translatedTitle': '反转字符串中的单词 III', 'titleSlug': 'reverse-words-in-a-string-iii', '__typename': 'QuestionNode'}, 'submitTime': 1541684697, '__typename': 'FeedSubmissionNode'}
{'status': 'A_12', 'lang': 'A_1', 'question': {'questionFrontendId': '557', 'title': 'Reverse Words in a String III', 'translatedTitle': '反转字符串中的单词 III', 'titleSlug': 'reverse-words-in-a-string-iii', '__typename': 'QuestionNode'}, 'submitTime': 1541683934, '__typename': 'FeedSubmissionNode'}
{'status': 'A_11', 'lang': 'A_1', 'question': {'questionFrontendId': '557', 'title': 'Reverse Words in a String III', 'translatedTitle': '反转字符串中的单词 III', 'titleSlug': 'reverse-words-in-a-string-iii', '__typename': 'QuestionNode'}, 'submitTime': 1541683208, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '344', 'title': 'Reverse String', 'translatedTitle': '反转字符串', 'titleSlug': 'reverse-string', '__typename': 'QuestionNode'}, 'submitTime': 1541680402, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '657', 'title': 'Robot Return to Origin', 'translatedTitle': '机器人能否返回原点', 'titleSlug': 'robot-return-to-origin', '__typename': 'QuestionNode'}, 'submitTime': 1541679592, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '709', 'title': 'To Lower Case', 'translatedTitle': '转换成小写字母', 'titleSlug': 'to-lower-case', '__typename': 'QuestionNode'}, 'submitTime': 1541679003, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '169', 'title': 'Majority Element', 'translatedTitle': '多数元素', 'titleSlug': 'majority-element', '__typename': 'QuestionNode'}, 'submitTime': 1541468091, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '169', 'title': 'Majority Element', 'translatedTitle': '多数元素', 'titleSlug': 'majority-element', '__typename': 'QuestionNode'}, 'submitTime': 1541467993, '__typename': 'FeedSubmissionNode'}
{'status': 'A_11', 'lang': 'A_1', 'question': {'questionFrontendId': '169', 'title': 'Majority Element', 'translatedTitle': '多数元素', 'titleSlug': 'majority-element', '__typename': 'QuestionNode'}, 'submitTime': 1541467975, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '905', 'title': 'Sort Array By Parity', 'translatedTitle': '按奇偶排序数组', 'titleSlug': 'sort-array-by-parity', '__typename': 'QuestionNode'}, 'submitTime': 1541467166, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '56', 'title': 'Merge Intervals', 'translatedTitle': '合并区间', 'titleSlug': 'merge-intervals', '__typename': 'QuestionNode'}, 'submitTime': 1541304121, '__typename': 'FeedSubmissionNode'}
{'status': 'A_15', 'lang': 'A_1', 'question': {'questionFrontendId': '56', 'title': 'Merge Intervals', 'translatedTitle': '合并区间', 'titleSlug': 'merge-intervals', '__typename': 'QuestionNode'}, 'submitTime': 1541303998, '__typename': 'FeedSubmissionNode'}
{'status': 'A_10', 'lang': 'A_1', 'question': {'questionFrontendId': '524', 'title': 'Longest Word in Dictionary through Deleting', 'translatedTitle': '通过删除字母匹配到字典里最长单词', 'titleSlug': 'longest-word-in-dictionary-through-deleting', '__typename': 'QuestionNode'}, 'submitTime': 1541133662, '__typename': 'FeedSubmissionNode'}

数据的初步获取就到此结束了。

你可能感兴趣的:(其他)