A.需求来源与分析
需求来源于生活,对于只是偶尔有兴趣做做题的我,力扣的每日一题对我一直有以下的不便:
太简单不想做,需要花太多时间的不想做,每天打开力扣其实只是想看一下是什么题,有意思才做。
看题需要打开电脑,而且打开电脑也不一定记得要去看看题,要是能把每日一题直接推送到我邮箱里就好了,这样每天起床的时候就能在手机的邮箱里看一看每日一题,如果确实有意思,再打开电脑的时候去做。
有几天没做后,甚至会忘记力扣的每日一题这件事情,然后就是很长一段时间不会去做题。
其实有些需求主要是因为我懒(bushi),但是,程序员要学会偷懒!于是我简单整理了一下我的需求:
每天早上的某个时候(最好是我起床的时候),能把每日一题推送到我的邮箱。
我能直接通过邮箱看到题目什么难度、考察哪些点、题目的内容,并且能直接点一下就进入题目。
说干就干,开工!于是花了一点时间把这样一个小玩意儿弄出来了。
B.技术角度分析
其实这件事情很简单,我只需要分析出力扣上每日一题的接口,然后写个python脚本把题目信息拿到,然后用smtp协议给我自己发封邮件即可,将这个脚本写入我服务器的crontab上,每天早上就自己跑了。接下来按照这些部分去分析即可。
C.具体分析步骤
大致就是:分析接口协议->获取题目信息->写脚本将信息发送到我的邮箱->将这个接口写入crontab。
1.接口协议分析
开始之前先讲点武德,我们先看一下力扣的robots.txt,看看哪些是不能爬的:
然后再去抓个包,看下哪个包最后得到了每日一题的数据:
发现这个包请求的结果就是每日一题:
幸运的是,这个接口并没有在上述robots.txt中,我们可以写个脚本模拟一下这个发包,注意到请求头中有csrf-token:
稍微找一下,可以发现返回包的cookie里面就带有csrf-token,所以我们提前请求一下即可,就可以从cookie中拿到csrf-token了。
这个请求体,很明显是graphql的请求参数,仔细看一下,发现并不需要传啥参数,所以直接调用即可。
再去看一下进入题目页面时候的关键请求接口,可以找到是这个接口:
返回的数据都是json,格式化一下就可以找到关键数据。
2.发邮件
此篇若有不清楚的见下面使用说明
3.写crontab放服务器上定时跑
每天上午11点自动提醒:(替换成自己的路径)
crontab –e
0 11 * * * python3 /home/atfwus/sheduler-script/lc-today-question/lc-day-title.py
1
2
D.成品
1.源代码
lc-day-title.py,自动采集每日一题的数据并发邮件提醒:
import requests,json,time
session = requests.session()
lc_url = ‘https://leetcode.cn’
graphql_url = ‘/graphql’
def int_csrf():
session.get(lc_url + graphql_url)
int_csrf()
user_agent = r’Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36’
headers = {
‘x-requested-with’ : ‘XMLHttpRequest’,
‘accept’ : ‘/’,
‘User-Agent’: user_agent,
‘Connection’: ‘keep-alive’,
‘origin’: ‘https://leetcode.cn’,
‘Content-Type’ :‘application/json’,
‘X-Csrftoken’: session.cookies[‘csrftoken’]
}
def get_today_question():
param = ‘’’
query questionOfToday {
todayRecord {
date
userStatus
question {
questionId
frontendQuestionId: questionFrontendId
difficulty
title
titleCn: translatedTitle
titleSlug
paidOnly: isPaidOnly
freqBar
isFavor
acRate
status
solutionNum
hasVideoSolution
topicTags {
name
nameTranslated: translatedName
id
}
extra {
topCompanyTags {
imgUrl
slug
numSubscribed
}
}
}
lastSubmission {
id
}
}
}
‘’’
data = {
"query": param,
"variables": {}
}
r = session.post(lc_url+graphql_url, headers=headers, data=json.dumps(data))
return r.json()
def get_one_question(title_slug):
param = ‘’’
query questionData($titleSlug: String!) {
question(titleSlug: $titleSlug) {
questionId
questionFrontendId
categoryTitle
boundTopicId
title
titleSlug
content
translatedTitle
translatedContent
isPaidOnly
difficulty
likes
dislikes
isLiked
similarQuestions
contributors {
username
profileUrl
avatarUrl
__typename
}
langToValidPlayground
topicTags {
name
slug
translatedName
__typename
}
companyTagStats
codeSnippets {
lang
langSlug
code
__typename
}
stats
hints
solution {
id
canSeeDetail
__typename
}
status
sampleTestCase
metaData
judgerAvailable
judgeType
mysqlSchemas
enableRunCode
envInfo
book {
id
bookName
pressName
source
shortDescription
fullDescription
bookImgUrl
pressImgUrl
productUrl
__typename
}
isSubscribed
isDailyQuestion
dailyRecordStatus
editorType
ugcQuestionId
style
exampleTestcases
jsonExampleTestcases
__typename
}
}
‘’’
data = {
“operationName”: “questionData”,
“variables”: {
“titleSlug”: title_slug
},
“query”: param
}
r = session.post(lc_url + graphql_url, headers=headers, data=json.dumps(data))
return r.json()
def send_to_mail(q, sf):
q = q[‘data’][‘question’]
id = q[‘questionFrontendId’]
title = q[‘translatedTitle’]
url = ‘https://leetcode.cn/problems/’ + q[‘titleSlug’]
ac_rate = ‘{:.2%}’.format(sf[‘acRate’])
def generate_subject():
day_str = time.strftime(‘%m月%#d日’, time.localtime(time.time()))
return f’力扣{day_str}每日一题来咯!!!({id}.{title})’
print(generate_subject())
def generate_plain():
content = q['translatedContent']
difficulty = q['difficulty']
tags = []
for i in q['topicTags']:
tags.append(i['translatedName'])
tags_str = ' '.join(tags)
return f'''
题目名称:{id}.{title}     题目难度:{difficulty}     AC率:{ac_rate}     题目链接:{url}
题目标签:{tags_str}
{content}
'''.strip()
print(generate_plain())
from mail import send_mail
send_mail(subject=generate_subject(), plain=generate_plain())
sf = get_today_question()
s = get_one_question(sf[‘data’][‘todayRecord’][0][‘question’][‘titleSlug’])
send_to_mail(s, sf[‘data’][‘todayRecord’][0][‘question’])
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
mail.py,用于发邮件:
import smtplib
from email.mime.text import MIMEText
from email.header import Header
def init_con():
# 创建 SMTP 对象
smtp = smtplib.SMTP()
# 连接(connect)指定服务器
smtp.connect(“smtp.qq.com”, port=25)
# 登录,需要:登录邮箱和授权码
smtp.login(user=““, password=””)
return smtp
def send_mail(subject, plain):
smtp = init_con()
# 构造MIMEText对象,参数为:正文,MIME的subtype,编码方式
message = MIMEText(plain, 'html', 'utf-8')
message['From'] = Header("Leetcode 每日一题提醒 By ATFWUS", 'utf-8') # 发件人的昵称
message['To'] = Header("ATFWUS", 'utf-8') # 收件人的昵称
message['Subject'] = Header(subject, 'utf-8') # 定义主题内容
smtp.sendmail(from_addr="***", to_addrs="***", msg=message.as_string())
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
2.效果
每日一题来咯!!!
3.使用说明
只需要修改mail.py中的邮箱和密码即可,然后将两个py文件放在云服务器上,crontab定时任务自动执行。
下面是密码的获取方式:
qq邮箱中,点设置,在这个地方找到授权码,申请授权码,并填在上面mail.py脚本的password上。