标签: 油猴

油猴脚本,抓取LeetCode题解[3]

再更新一下,接之前的版本油猴脚本,抓取LeetCode题解[2],在原来的基础上增加了单独抓取单篇题解的功能,再做这个的时候其实遇到个问题,主要难点在于如何监听浏览器地址栏的变化,另开一个文章来说《无刷新页面监听URL变化实现

至于新增这个功能的原因如下,在使用初版的功能全量的抓取了自己的题解文章之后,对于每天新增的题解不能单独抓取补充到indexDB之中,只能再次全量抓取一下,显然不太方便,所以又改进了下增加了这样的功能

另外我试了下,这个抓取用户题解文章列表的接口是不做权限验证的,也就是说A用户可以抓取B用户的所有题解列表,我找了下使用场景,当A用户点开某个B用户的LeetCode个人主页的时候,下面有一块展示区域是防止当前B用户的题解列表的。这是一个正常使用的场景,也就是说,你可以抓取任何你喜欢的用户的所有题解列表

另外修改了一点样式。在右边单独建了小的Panel把按钮都放进去,到处分散的不大好看,

下一步内容,解析题目、题解中的图片标签,抓取图片内容并base64之后作为文本保存下来,使题解内容脱离对力扣图片资源的依赖

Continue reading

油猴脚本,抓取LeetCode题解[2]

接上篇,油猴脚本,抓取LeetCode题解,补充改版了下,顺便为啥要写这个呢?原因其实很简单,因为之前写了很多题解,都是直接在LeetCode上写的,加起来有快400篇了吧,然后自己这边博客每篇都要搬运一下,比较麻烦费事,所以才想着写了这么个工具。

这次改进做了如下

  1. 引入了IndexDBWrapper(https://www.npmjs.com/package/indexdbwrapper/v/1.0.4),封装了IndexDB的操作过程
  2. 直接任何力扣页面都可以操作了,账户需要登录
  3. 直接抓取我的题解了列表,之后根据列表再抓取题目和自己写的题解
  4. 使用了CustomEvent,抓取了列表之后,发起一个事件,页面同时监听事件,得到列表后进行抓取详细内容
  5. 定义了MY_ACCOUNT,需要手动替换为自己的账号ID
  6. 完成后点击下载按钮生成一个json文件下载
  7. 代码中对返回结果内容的字段进行了一些删除,删掉不需要的字段信息,缩小下载文件的大小

之后要做的事情,既然之前自己的写的题解已经拿到了,那么就是想办法再展示出来了,下一步可能想把下载得到的文件通过一个前端项目展示出来,不过这里还是有点问题,部分题解中含有图片内容,这个内容还是存在LeetCode服务器上的,这个还需要另外再想办法获取下来

Continue reading

油猴脚本,抓取LeetCode题解

用来抓取自己之前写的LeetCode刷题写的题解

// ==UserScript==
// @name         LeetCode Test
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://leetcode.cn/problems/*/solution/*
// @grant        none
// ==/UserScript==





(function() {
    'use strict';
    let createButton = (func, btnText, top) => {
        let btn = document.createElement("button")
        btn.style.position = "fixed"
        btn.style.right = 0
        // btn.style.top='30%'
        btn.style.top = top
        btn.style.padding = "10px"
        btn.style.zIndex = 99999
        btn.innerText = btnText
        btn.addEventListener("click", func)
        document.body.append(btn)
    }

    let dbName = 'solutionArticle', version = 1, storeName = 'mySolution'

    let indexedDB = window.indexedDB
    let db
    const request = indexedDB.open(dbName, version)
    request.onsuccess = function(event) {
        db = event.target.result // 数据库对象
        console.log('数据库打开成功')
    }

    request.onerror = function(event) {
        console.log('数据库打开报错')
    }

    request.onupgradeneeded = function(event) {
        // 数据库创建或升级的时候会触发
        console.log('onupgradeneeded')
        db = event.target.result // 数据库对象
        let objectStore
        if (!db.objectStoreNames.contains(storeName)) {
            objectStore = db.createObjectStore(storeName, { keyPath: 'questionFrontendId' }) // 创建表
            // objectStore.createIndex('name', 'name', { unique: true }) // 创建索引 可以让你搜索任意字段
        }
    }
    let saveToIndeDB = (data)=>{
        let request = db.transaction([storeName], 'readwrite') // 事务对象 指定表格名称和操作模式("只读"或"读写")
        .objectStore(storeName) // 仓库对象
        .add(data)

        request.onsuccess = function(event) {
            console.log('数据写入成功')
        }

        request.onerror = function(event) {
            console.log('数据写入失败')
            throw new Error(event.target.error)
        }
    }
    let cursorGetData = () =>{
        let list = []
        let store = db.transaction(storeName, 'readwrite') // 事务
        .objectStore(storeName) // 仓库对象
        let request = store.openCursor() // 指针对象
        return new Promise((resolve, reject) => {
            request.onsuccess = function(e) {
                let cursor = e.target.result
                if (cursor) {
                    // 必须要检查
                    list.push(cursor.value)
                    cursor.continue() // 遍历了存储对象中的所有内容
                } else {
                    resolve(list)
                }
            }
            request.onerror = function(e) {
                reject(e)
            }
        })
    }

    createButton( ()=>{
        cursorGetData().then(list=>{
            console.log(list)
        })
    } , "列表", "160px")




    let key = ''
    let addLocal = (k,v)=>{
        let oldJson = localStorage.getItem(key)
        if(null==oldJson){
            oldJson = {}
        }else{
            oldJson = JSON.parse(oldJson);
        }
        oldJson[k] = v;
        localStorage.setItem(key,JSON.stringify(oldJson))
    }
    let getQuestion = (slug)=>{
        let p = {
            "operationName":"questionData",
            "variables":{"titleSlug":slug},
            "query":"query questionData($titleSlug: String!) {\n  question(titleSlug: $titleSlug) {\n    questionId\n    questionFrontendId\n    categoryTitle\n    boundTopicId\n    title\n    titleSlug\n    content\n    translatedTitle\n    translatedContent\n    isPaidOnly\n    difficulty\n    likes\n    dislikes\n    isLiked\n    similarQuestions\n    contributors {\n      username\n      profileUrl\n      avatarUrl\n      __typename\n    }\n    langToValidPlayground\n    topicTags {\n      name\n      slug\n      translatedName\n      __typename\n    }\n    companyTagStats\n    codeSnippets {\n      lang\n      langSlug\n      code\n      __typename\n    }\n    stats\n    hints\n    solution {\n      id\n      canSeeDetail\n      __typename\n    }\n    status\n    sampleTestCase\n    metaData\n    judgerAvailable\n    judgeType\n    mysqlSchemas\n    enableRunCode\n    envInfo\n    book {\n      id\n      bookName\n      pressName\n      source\n      shortDescription\n      fullDescription\n      bookImgUrl\n      pressImgUrl\n      productUrl\n      __typename\n    }\n    isSubscribed\n    isDailyQuestion\n    dailyRecordStatus\n    editorType\n    ugcQuestionId\n    style\n    exampleTestcases\n    jsonExampleTestcases\n    __typename\n  }\n}\n"
        }
        let options = {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(p)
        }
        fetch("https://leetcode.cn/graphql/",options).then((res)=>{
            if(res.ok){
                //如果取数据成功
                res.json().then((data)=>{
                    //转化为json数据进行处理
                    console.log(data.data)
                    let question = data.data.question
                    addLocal('questionTitle',question.translatedTitle)
                    addLocal('questionFrontendId',question.questionFrontendId)
                    addLocal('questionFullTittle',"LeetCode刷题【"+question.questionFrontendId+"】"+question.translatedTitle)
                    let questionContentAppend = "<div>Related Topics</div><div><ul>"
                    let questionTags = [];
                    for(let tag of question.topicTags){
                        questionTags.push(tag.translatedName);
                        questionContentAppend += "<li>"+tag.translatedName+"</li>"
                    }
                    questionContentAppend += "</ul></div></div><br><div><li>👍 "+question.likes+"</li><li>👎 "+question.dislikes+"</li></div>"
                    addLocal('questionContent',question.translatedContent+questionContentAppend)
                    addLocal('questionTags',questionTags)
                    addLocal('questionTagsStr',"算法,LeetCode,"+questionTags.join(",")+",")
                    saveToIndeDB(JSON.parse(localStorage.getItem(key)))

                })
            }else{
                console.log(res.status);
                //查看获取状态
            }
        }).catch((res)=>{
            //输出一些错误信息
            console.log(res.status);
        })
    }
    let pathname = window.location.pathname
    let param = {
        "operationName":"solutionDetailArticle",
        "variables":{
            "slug":pathname.split("/")[4],
            "orderBy":"DEFAULT"
        },
        "query":"query solutionDetailArticle($slug: String!, $orderBy: SolutionArticleOrderBy!) {\n  solutionArticle(slug: $slug, orderBy: $orderBy) {\n    ...solutionArticle\n    content\n    question {\n      questionTitleSlug\n      __typename\n    }\n    position\n    next {\n      slug\n      title\n      __typename\n    }\n    prev {\n      slug\n      title\n      __typename\n    }\n    __typename\n  }\n}\n\nfragment solutionArticle on SolutionArticleNode {\n  rewardEnabled\n  canEditReward\n  uuid\n  title\n  slug\n  sunk\n  chargeType\n  status\n  identifier\n  canEdit\n  canSee\n  reactionType\n  reactionsV2 {\n    count\n    reactionType\n    __typename\n  }\n  tags {\n    name\n    nameTranslated\n    slug\n    tagType\n    __typename\n  }\n  createdAt\n  thumbnail\n  author {\n    username\n    profile {\n      userAvatar\n      userSlug\n      realName\n      __typename\n    }\n    __typename\n  }\n  summary\n  topic {\n    id\n    commentCount\n    viewCount\n    __typename\n  }\n  byLeetcode\n  isMyFavorite\n  isMostPopular\n  isEditorsPick\n  hitCount\n  videosInfo {\n    videoId\n    coverUrl\n    duration\n    __typename\n  }\n  __typename\n}\n"
    }
    let options = {
        method: 'POST',
        headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(param)
    }
    fetch("https://leetcode.cn/graphql/",options).then((res)=>{
        if(res.ok){
            //如果取数据成功
            res.json().then((data)=>{
                //转化为json数据进行处理
                //console.log(data);
                let {data:solutionArticle} = data
                let {solutionArticle:{author,content,question,title}} = solutionArticle
                if(author.username != "cheungq-6"){
                    return
                }
                console.log(solutionArticle)
                getQuestion(question.questionTitleSlug)
                key = '_____'+question.questionTitleSlug
                console.error("key:"+key)
                addLocal('title',title)
                addLocal('content',title+"\n"+content)
                addLocal('slug',question.questionTitleSlug)
            })
        }else{
            console.log(res.status);
            //查看获取状态
        }
    }).catch((res)=>{
        //输出一些错误信息
        console.log(res.status);
    })
    // Your code here...
})();

抓到的数据最终存到indexdDB中,中间存了下localStorage,后来改的存indexdDB,但是没删掉中间存localStorage的过程

油猴脚本,公众号文章图片显示

常看公众号文章的时候,遇到一个恼人的问题,图片不会加载出来,需要手动点击一下,(公司网络相关策略,不能登微信,但是可以打开公众号文章链接)。另外也是网络原因,公众号内的https链接的图片无法打开,于是萌生了自己写个油猴脚本,自动加载图片的想法。

简单看了下页面dom结构。思路很简单,遍历下img标签,取到data-src属性,这里存了真实的图片url,然后替换掉原来的src属性里的值,这里默认是一个灰色占位图片。触发方法也很简单。每次scroll事件的时候触发。那么,直接开始撸代码

// ==UserScript==
// @name         公众号文章宽屏&图片
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://mp.weixin.qq.com/s*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    let func = ()=>{
        let arr = document.getElementsByTagName("img");
        for(let i = 0; i < arr.length-1; i++){
            let src = arr[i].getAttribute("src");
            let dataSrc = arr[i].getAttribute("data-src")
            dataSrc = dataSrc!=null?dataSrc.replace("https://","http://"):'';
            if(src != undefined && src.startsWith("http")){
                arr[i].setAttribute("src",src.replace("https://","http://"))
            }
            if(src != undefined && src.startsWith("data:")){
                arr[i].setAttribute("src",dataSrc)
            }
        }
    }

    window.addEventListener("scroll",func)
    document.getElementsByClassName("rich_media_area_primary_inner")[0].style.width = "100%"
    document.getElementsByClassName("rich_media_area_primary_inner")[0].style.maxWidth = "1080px"
})();

后续:买了个BeeArt会员,想在公司电脑上用下画点图之类的,但是网站直接打不开,看了下报错信息,IndexedDB的问题,由于公司电脑上的Chrome是公司预装的,怀疑是版本太旧的原因,重新安装了89版本的Chrome,果然解决。但是。。。新问题出现了,原先替换https为http链接的图片无法显示了

Mixed Content: The page at 'https://mp.weixin.qq.com/s/L39ufU9YCVIJ9g1WCVQAGQ' was loaded over HTTPS, but requested an insecure element 'http://mmbiz.qpic.cn/mmbiz_png/aaVJqS7LaMJQfIuic9SSvvZFP1hqaKk43iaDicQj9R6dNjpTcf7DgkXdk2v4CxSTUZMxpqkWRCia8jUiaAK2iaPicl9kA/640?wx_fmt=png'. This request was automatically upgraded to HTTPS, For more information see https://blog.chromium.org/2019/10/no-more-mixed-messages-about-https.html

一直报这个问题,看了下网络,即使是用的http的图片链接,但是实际请求的还是https链接,查证了一下原来在某个新版本的Chrome中,  开始阻止混合内容 。https的网页中资源链接,默认都转成https的请求。

解决方法:点击地址栏前面的“△不安全”或者锁形图标,有个网站设置选项,找到“不安全内容”,选择运行,这样就又恢复了原来的情况,可以正常访问了。我是个人特例情况,所以我是这么解决的

正确方法:网站在域名做了SSL之后,那么相应配套的资源链接也应该全部切换到SSL,全站HTTPS势在必行