《Acid 免责声明》/ Acid Disclaimer

《Acid 免责声明》

  1. 性质声明
    Acid 仅为技术演示工具,所有请求均模拟浏览器行为,不修改、不存储任何目标网站数据。
  2. 合法使用
    禁止用于:
    • 绕过付费内容限制
    • 攻击性爬虫
    • 违反《计算机信息网络国际联网安全保护管理办法》的行为
  3. 即时失效
    部分网站可能随时升级防护,导致工具失效,不提供维护承诺
  4. 风险警示
    使用后若出现:
    • IP 被目标站封禁
    • 法律风险
      自行承担全部责任

Acid Disclaimer

  1. Nature
    Acid is a technical demo ONLY. It simulates browser requests without modifying/storing any target site data.
  2. Legal Use
    Prohibited for:
    • Bypassing paywalls
    • Aggressive scraping
    • Violating cybersecurity laws
  3. No Guarantee
    Tool may break if targets update protections. No maintenance promised.
  4. Warning
    You risk:
    • IP bans by targets
    • Legal consequences
      All liabilities are on you.

Copyright 2025 by PanDaoxi.

我无意中翻到了曾经的文章《在线文档 Docsify 的简单应用 | PanDaoxi 的博客》,发现文章中提到:

由于文章太多,不能一个个地把图片弄下来,也不能手动修改每一个错误的文件名。
所以我使用了 Python 做批处理的活。这个 Python 文件的目的有以下几个:

  1. 匹配图片,替换图片;
  2. 修改文件名。
    Luckily,我发现一个可以反防盗链的 API。只需要在链接前面添加上 https://pic.2ge.org/cdn/?url= 即可。

如此关键技术,岂能受制于人!

所以我就自己写了一个 netlify functions 接口,实现这个功能。

仓库见 https://github.com/PanDaoxi/acid;部署方法请参考 https://github.com/PanDaoxi/PicFlare/blob/master/README.md


这个程序主要是 deepseek 写的,我略微优化了一下。以下是 deepseek 对于我的程序的解释:

它就像个「超级变装间谍」

  1. 任务:帮你去别人网站“偷”图片(比如 CSDN 的图)
  2. 难点:对方保安(防盗链系统)会检查:
    • 你是不是从自家网站跳转来的(Referer 头)
    • 你用的是不是正规浏览器(User-Agent 头)
    • 你的 IP 是不是黑名单里的(比如海外 IP)

间谍的破解手段

  1. 第一招:伪装来源

    • 假装是从百度/谷歌跳转来的(改 Referer

      1
      headers: { 'Referer': 'https://www.baidu.com' }
  2. 第二招:换浏览器皮肤

    • 假装自己是 Chrome 浏览器(改 User-Agent

      1
      headers: { 'User-Agent': 'Mozilla/5.0 (Chrome...)' }
  3. 第三招:伪造身份证

    • 用中国 IP 地址(随机生成国内 IP)

      1
      headers: { 'X-Forwarded-For': '116.25.123.45' }
  4. 终极绝招:多套方案轮流试

    • 如果第一套伪装失败,自动换第二套方案
    • 就像间谍被识破后立刻换新身份

程序工作流程


为什么不会崩溃?

  1. 每步都有备用方案
    • 就像间谍随身带 5 本不同护照
  2. 连错误都有应对
    • 如果所有方案都失败,至少返回一张错误提示图
  3. 自动维护工具
    • 定期更新伪装参数(比如更换最新浏览器版本号)

实际效果

  • 输入:https://acid.daoxi365.dpdns.org/proxy?url=图片地址
  • 输出:直接显示被保护的图片
  • 成功率:绝大部分常见网站都能突破

简单说,它就是通过不断变换伪装身份,骗过网站的防盗链检查!

核心的程序就是 functions/proxy/proxy.js,本质上是个代理程序。如下:

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
// 工具函数定义
const generateChinaIP = () => {
const pools = [
`116.25.${Math.floor(Math.random() * 255)}.${Math.floor(
Math.random() * 255
)}`, // 广东电信
`220.181.${Math.floor(Math.random() * 255)}.${Math.floor(
Math.random() * 255
)}`, // 北京百度
`111.206.${Math.floor(Math.random() * 255)}.${Math.floor(
Math.random() * 255
)}`, // 北京联通
];
return pools[Math.floor(Math.random() * pools.length)];
};

const getMimeTypeFromUrl = (url) => {
const extension = url.split(".").pop().toLowerCase();
const types = {
jpg: "image/jpeg",
jpeg: "image/jpeg",
png: "image/png",
gif: "image/gif",
webp: "image/webp",
svg: "image/svg+xml",
};
return types[extension] || "application/octet-stream";
};

const buildHeaders = (url, config, strategy) => {
const headers = {
...config.default?.headers,
...strategy.headers,
Host: new URL(url).hostname,
};

// 动态处理函数式header
Object.entries(headers).forEach(([key, value]) => {
if (typeof value === "function") {
headers[key] = value(url);
}
});

// 添加cookies
if (config.cookies) {
headers.Cookie = Object.entries(config.cookies)
.map(([k, v]) => `${k}=${v}`)
.join("; ");
}

return headers;
};

// 动态策略配置
const DYNAMIC_CONFIG = {
default: {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
Accept: "image/webp,image/apng,image/*,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
},
strategies: [
{
name: "google-referer",
headers: { Referer: "https://www.google.com/" },
},
{
name: "target-origin",
headers: { Referer: (url) => `${new URL(url).origin}/` },
},
{
name: "social-media",
headers: {
Referer: "https://www.facebook.com/",
"X-Forwarded-For": generateChinaIP(),
},
},
],
},
"csdnimg.cn": {
cookies: {
dc_session_id: Date.now().toString(36),
dc_tracker: "true",
},
strategies: [
{
name: "baidu-referer",
headers: { Referer: "https://www.baidu.com/" },
},
],
},
};

// 响应辅助函数
const methodNotAllowed = () => ({
statusCode: 405,
body: "Method Not Allowed",
});

const badRequest = (message) => ({
statusCode: 400,
body: message,
});

const getSiteConfig = (hostname) => {
const domain = hostname.replace("www.", "");
return DYNAMIC_CONFIG[domain] || DYNAMIC_CONFIG.default;
};

// 主处理函数
exports.handler = async (event) => {
if (event.httpMethod !== "GET") return methodNotAllowed();

const targetUrl = event.queryStringParameters.url;
if (!targetUrl) return badRequest("Missing URL parameter");

try {
const { hostname } = new URL(targetUrl);
const siteConfig = getSiteConfig(hostname);

let lastError;
for (const strategy of siteConfig.strategies) {
try {
const headers = buildHeaders(targetUrl, siteConfig, strategy);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);

const response = await fetch(targetUrl, {
headers,
signal: controller.signal,
redirect: "follow",
});

clearTimeout(timeout);

if (response.ok) {
const buffer = await response
.arrayBuffer()
.then(Buffer.from);
return {
statusCode: 200,
headers: {
"Content-Type":
response.headers.get("content-type") ||
getMimeTypeFromUrl(targetUrl),
"Cache-Control": "public, max-age=86400",
"Access-Control-Allow-Origin": "*",
"X-Proxy-Strategy": strategy.name,
},
body: buffer.toString("base64"),
isBase64Encoded: true,
};
}

lastError = {
status: response.status,
statusText: response.statusText,
};
} catch (error) {
lastError = error;
}
}

return {
statusCode: lastError.status || 502,
body: lastError.statusText || "Proxy Gateway Error",
};
} catch (error) {
console.error("Proxy error:", error);
return {
statusCode: 500,
body: `Internal Server Error: ${error.message}`,
};
}
};

(可能由于更新会有些变动。)

例子:

1
2
3
https://i-blog.csdnimg.cn/img_convert/e59df5717e5e79f83b1090827e593e78.png
改为
https://acid.daoxi365.dpdns.org/api/proxy?url=https://i-blog.csdnimg.cn/img_convert/e59df5717e5e79f83b1090827e593e78.png

其实,如果你把它当成代理用,也当然可以;

你甚至可以访问 https://acid.daoxi365.dpdns.org/api/proxy?url=https://github.com/PanDaoxi/acid,这一页面也可以正常加载!

(仅限于部分页面。如我的博客使用大量的相对路径,这就会导致 404 Not Found。)

仓库见 https://github.com/PanDaoxi/acid