• 【爬虫】力扣每日一题每天自动邮件提醒!!!


    使用python实现了一个力扣每日一题每天自动邮件提醒的小爬虫,小但实用!!!



    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}&nbsp&nbsp&nbsp&nbsp&nbsp题目难度:<strong>{difficulty}</strong>&nbsp&nbsp&nbsp&nbsp&nbspAC率:{ac_rate}&nbsp&nbsp&nbsp&nbsp&nbsp题目链接:<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'])
    
    • 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上。

    在这里插入图片描述


    免责申明

    本文仅供技术交流学习使用,严禁使用于任何商业用途,若有任何侵权行为请联系我删除!


    ATFWUS 2022-12-05

  • 相关阅读:
    excel功能区(ribbonx)编程笔记--3 editbox与状态按钮togglebutton控件
    Python 奇淫技巧,助你更好的摸鱼
    22-07-27 西安 ElasticSearch(02)
    twitter推文采集案例
    springboot- redis常见数据存取
    IPD各阶段交付文档
    torch.stack
    【直播精彩回顾】数据改变社会,BI助力发展!
    10.27 知识总结(前端)
    10道不得不会的 Java容器 面试题
  • 原文地址:https://blog.csdn.net/ATFWUS/article/details/128192288