使用python实现了一个力扣每日一题每天自动邮件提醒的小爬虫,小但实用!!!
文章目录
- A.需求来源与分析
- B.技术角度分析
- C.具体分析步骤
- 1.接口协议分析
- 2.发邮件
- 3.写crontab放服务器上定时跑
- D.成品
- 1.源代码
- 2.效果
- 3.使用说明
- 免责申明
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
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}     题目难度:<strong>{difficulty}</strong>     AC率:{ac_rate}     题目链接:<a href="{url}">{url}</a><br>
题目标签:{tags_str}<br>
{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'])
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())
2.效果
每日一题来咯!!!
3.使用说明
只需要修改mail.py中的邮箱和密码即可,然后将两个py文件放在云服务器上,crontab定时任务自动执行。
下面是密码的获取方式:
qq邮箱中,点设置,在这个地方找到授权码,申请授权码,并填在上面mail.py脚本的password上。
免责申明
本文仅供技术交流学习使用,严禁使用于任何商业用途,若有任何侵权行为请联系我删除!
ATFWUS 2022-12-05