1、首先是插件篇:

1.1 manifest.json

{
    "manifest_version": 3,
    "name": "My Douyin Downloader",
    "version": "1.1",
    "description": "My Douyin Downloader",
    "content_scripts": [
      {
        "matches": ["https://*.douyin.com/*"],
        "js": ["content.js"]
      }
    ],
    "permissions": ["activeTab", "webRequest", "downloads"],
    "host_permissions": ["https://*.douyin.com/*", "https://*.zjcdn.com/*","https://*.douyinvod.com/*"],
    "web_accessible_resources": [
      {
        "resources": ["assets/*", "popup.js", "popup.css"],
        "matches": ["https://douyin.com/*", "https://www.douyin.com/*","https://*.douyinvod.com/*"]
      }
    ],
    "background": {
      "service_worker": "background.js"
    },
    "action": {
      "default_icon": {
        "16": "assets/images/icon.png",
        "48": "assets/images/icon.png",
        "128": "assets/images/icon.png"
      }
    }
  }

1.2 content.js

(function() {
    'use strict';
    document.addEventListener("keyup", function (event) {
        // 79 是 o 键的键码
        if (event.keyCode === 79 || event.keyCode === 111) {
            //let videoInfoDetailElement = document.querySelector('.video-info-detail');
            //let videoId = videoInfoDetailElement.getAttribute('data-e2e-aweme-id');
            let targetDivDom = document.querySelector('[data-e2e="feed-active-video"]');
            let videoId = targetDivDom.getAttribute("data-e2e-vid");
            console.log(videoId);
            alert("你好!"+videoId);
            let p = {};
            p.videoId = videoId;
            chrome.runtime.sendMessage(p, function (response) {
              if (chrome.runtime.lastError) {
                alert("chrome.runtime.lastError")
                console.log(chrome.runtime.lastError.message);
              } else {
                if (response.status === "ok") {
                    console.log(response.videoLink)
                                  let data ={};
                                  data.videoId  = videoId;
                                  data.videoUrl =response.videoLink;
                                  console.log("抖音下载小助手,下载前的payload构造打印:");
                                  console.log(data);
                                  var xhr = new XMLHttpRequest();
                                  xhr.open("POST", "http://localhost:8000/videos", true);
                                  xhr.setRequestHeader("Content-Type", "application/json");
                                  xhr.onreadystatechange = function() {
                                    if (xhr.readyState == 4 && xhr.status == 200) {
                                      var response = xhr.responseText;
                                      console.log(response);
                                    }
                                  };
                                  var payload = JSON.stringify(data);
                                  xhr.send(payload);
                } else {
                    alert("failed from background")
                }
              }
            });
        }
    });
})();

1.3 background.js

//background.js
let urlList = [];
chrome.webRequest.onBeforeRequest.addListener(
  (details) => {
    const link = details.url;
    //if (link.includes("zjcdn") && urlList.at(-1) !== link) {
    //   if (urlList.length > 50) {
    //     urlList.shift();
    //   }
      urlList.push(link);
    //}
  },
  { urls: ["https://*.douyinvod.com/*"] }
);
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
    console.log(request);
    const videoLink = urlList.find((link) => link.includes(request.videoId));
    sendResponse({ status: "ok", videoLink:videoLink});
    // Return true to keep the message channel open
    return true;
});


========================================================

2、插件这边就算完成了,接着是python这边


from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import requests
app = FastAPI()
def downloadVideo(url,filename):
    response =requests.get(url,stream=True)
    response.raise_for_status()
    with open(filename,"wb") as file:
        for chunk in response.iter_content(chunk_size=8192):
            if chunk:
                file.write(chunk)
class Video(BaseModel):
    videoId: str
    videoUrl: str
# 因为是xxxx发起的,所以需要要把抖音的这个地址加进去    
origins = [
    "http://localhost.tiangolo.com",
    "https://localhost.tiangolo.com",
    "http://www.douyin.com",
    "https://www.douyin.com",
    "http://localhost",
    "http://localhost:8080",
]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
@app.post("/videos")
async def main(video: Video):
    downloadVideo(video.videoUrl,video.videoId+".mp4")
    return video
#用这个去启动
#source .venv/bin/activate
#uvicorn postserver:app --reload
#pip install fastapi-cors


3、使用

先加载插件,然后激活,这样x的web版本就可以加载这个插件了,background监听到具体的url后,建立一个list

然后,在content.js里,按下o键,就会激发过程

查到url后,将

data.videoId  = videoId;
data.videoUrl =response.videoLink;

发给python这边来负责下载

之所以要这么写也是因为,这样可以前后端分离,稍后可以把python部署到nas那边去

其外是,我不太相信js的下载能力

我也不想下载到本机

python这边需要注意的技巧就是:

from fastapi.middleware.cors import CORSMiddleware

# 因为是xxx发起的,所以需要要把抖音的这个地址加进去    
origins = [
    "http://localhost.tiangolo.com",
    "https://localhost.tiangolo.com",
    "http://www.douyin.com",
    "https://www.douyin.com",
    "http://localhost",
    "http://localhost:8080",
]
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

反正fastapi也有不少坑就是了,以上是摸索了好久搞定的CORS这块


from pydantic import BaseModel

class Video(BaseModel):
    videoId: str
    videoUrl: str
    
@app.post("/videos")
async def main(video: Video):

算是比较优雅的接受参数的方式了,还可以声明None什么的