2026-04-07 13:55:13
清明节假期还是如约而至了,跟着一起来的还有临近下班的时候收到的一条竞岗通知,让假期最后一天十二点之前提交竞岗申请表。
下班收到这条通知,我并不想现在去填那个申请表,还要领导签字之后上交。由他去吧,到家之后看到手机又有一些消息,打开大概浏览了一下,意思是,如果无法提交纸质版,或者领导无法签字,可以先提交电子版。跟对象提及此事,她说,『该交就交吧。该走的流程还是要走的,如果视在不行,那就拿补偿走人,也挺好的。现在我觉得,你能不能继续干下去都挺好的,能干就干,不能干就走。』稍微顿了以下,继续说:『找时间咱们去看看把公积金提出来吧,我同事都提了好多次了。不过咱们的契税单子没了,不知道能不能提。』
『嗐,担心那个干嘛,直接用手机申请下不就知道了』我一边说,一边去拿手机。支付宝打开公积金小程序,一堆查询走下去,并且有什么异常提示。尝试直接提取,理由选择偿还商业贷款。一步步操作,到最后验证贷款的时候,选择贷款银行,最后竟然只需要一个贷款时间和贷款金额就能查询到贷款信息了,这一点做得的确不错。选择金额之后,点击提交申请,没有提示需要提交任何资料。提交完了才发现,忘了选择银行卡了。竟然填写的是多年以前的交通银行的银行卡。
对象担心银行卡还能不能用,索性直接登录手机银行,发现申请提现的金额已经到账了。现在该想一下怎么处理这笔钱了,第一步想着再去存个定期。然而,前段时间存的一张定期的单子,五年年利率只有1.75。打开兴业银行的贷款明细,看了下贷款利率石3.2。 这还存什么定期,干脆还款得了。
从兴业银行的手机银行再申请提前还款,于是这一笔钱就在手里打个转,过几天就又成了别人的了。看了下贷款信息,70万,还了十年,一共出去了30万,实际剩余的贷款还剩50万。相当于交了10万利息。不过反过来想,这十年租房子十万也不够,两室一厅的房子,十年房租下来也得二十多万了。这么一想,还是得感谢对象的眼光,房子买的早,想尽办法借钱买了这套房子,也得感谢自己不是犟种,觉得租房子也可,没坚持租房子。
至于假期,其实也没什么好的打算,还是带宝子回老家。四月天,正好是在户外放飞的时候。
晚上教练还问,第一天要不要上网球课。刚开始想着可以下课之后再走,晚上八点多开始下雨,十一点多的时候雨逐渐变大,路上也慢慢有了积水,既然如此,那室外的网球场第二天可能也没法打了,不如干脆请假。对象最近牙疼,假期也就不跟着回了,找时间去查一下看看怎么处理。
回老家的路上,前一段还算是比较顺利,绕行机场高速,绕过了最堵的市区高速。然而,等往青银转的时候,提示拥堵距离五公里,磨蹭到匝道入口才发现,车流量实在是太大了,只有一个车道,所有的车不得不慢慢悠悠的往里蹭。高速路况除了这一段,剩下的基本倒是顺畅,下了高速之后,开始另一段拥堵。高速工作人员指挥右转车辆走应急车道。左转的两个车道就只能等交警指挥放行了。
远远的就能看到省道上密密麻麻的车,下高速之后虽然之后十几公里的路程,却开了接近五十分钟,这还是最后到镇上之后抄小道绕过了一部分拥堵路段。
到家之后,宝子的姐姐躲在屋里的帐篷里。就是在室内又搭了一个帐篷,藏在里面。早上走的匆忙,也没买什么东西,让宝子给老太太一个红包,这件事算是过去了。在回来之前还给宝子的姐姐定了一份外卖,拉丝芝士棒,说想吃这个。六个芝士棒拿出来之后,实际他就吃了一个。剩下的几个到了中午才被其他人吃掉。
虽然晚上还在下雨,但是假期第一天的天气还算不错,虽然气温低了点。中午收拾好东西,去上坟。山路边的地里,已经又开始种满了杨树苗,几年前禁止耕地种树的决定,现在看来应该是又被废弃了。现在杨树的价格,却一言难尽,很多租地种树的甚至连租地的钱都挣不出来。尽管如此,还是有大面积的耕地被种满了树苗,好处是不用怎么管理,稍微去外面干点活,总是比种那点粮食收入能好很多。偶尔在破败的院落边上能看到一树桃花,娇艳欲滴。
下午孩子们在玩的时候,突然记起来去年买的那条绳子和滑轮。年前的时候在院子里玩过几次,受限于场地只能拴在门框上,另外一头拴在了墙上。现在孩子们比去年肿了,尼龙绳子又有一定的弹力,稍微一拉伸可能就拖到地上了。想着去户外的树林子重新搭一套滑道,试了几棵树都不大行。这时候姐姐提议用邻居宅基地里面的那几棵树,刚好一头比较高,另外一头比较低。
搬梯子拴上去,试了以下,刚好。
几个孩子就这么在这个简易滑索上玩了大半个下午,剩下的时间爬门楼的平台,通过那个手工捆绑的梯子,爬上来,爬下去。有时候还要带着猫咪一起爬。
坐在月台上,猫咪和小狗就在身边嬉闹。
不过并不是总是那么和谐,有时候狗子也会直接张大嘴咬猫咪的脑袋,这时候猫咪就只剩下望风而逃了。
在家的日子过的也快,转眼一下午的时间就没了。晚上宝子跟她姐姐挤在那个帐篷里睡了一晚上,虽然地方小,但是睡得挺好,第二天早上九点多过去看的时候,还没起。等起床洗刷完依然过了十点,连早饭都省了。
中午包水饺,宝子跟她姐姐一起上阵,包的饺子挺好的,有模有样,这时候二姐说到:『你想想办法,把抽屉的锁给弄开吧,钥匙丢了,已经半年多没开了。』
这个锁其实已经换过一次了,上次也是钥匙丢了。用钢锯条锯开之后,换了一把新的,这次,自然也是同样的方法。出门骑电动车到镇上五金店,买了五根锯条,一把锁,一共花了七块五。老板对于我怎么锯开写字台的锁表示很好奇,我解答说:『就那么直接把锯条伸进去锯就行了。』我说完,他依然一脸不可置信。
到家,带上一副厚手套,大约五分钟,在崩了三根锯条之后终于成功了。
拆掉旧的,装上新的。
不过,这次买的锁头稍微小了一点,周边留出很多缝隙。也无所谓了,能用就行。给老太太留下一把钥匙,另外一把钥匙找地方放了起来。以防哪天钥匙又丢了。
下午吃完饭,宝子们又嚷嚷着要爬梯子。但是鉴于之前扣车上苹果模型的熊孩子还没走,自己就把梯子给撤了。然而,过了不一会儿,宝子跑进来说,滑索坏掉了,拖到地上了。用屁股想也知道,肯定是那个熊孩子上去了,目测近一百斤的体重,那一根尼龙绳子怎么能提供那么大的张力。绳子已经被全部拉了下来,关键是还怕熊孩子玩的时候万一受伤,说都说不清楚,只好把滑索给拆掉了。
下午跟姐姐带着宝子们去外面溜达,小狗也一直跟着,一会儿跑的无影无踪了,不知道什么时候又跑了回来,乐此不疲。路边开满了野花,孩子们也去折了一些。
田里也有些许忙碌的身影,在整理田地。有的在浇麦子,一个熊孩子在低头跑来跑去,看着我们过去,前面有个小狗。熊孩子拾起一块石头开始去追狗子,朝着狗子扔了过去。好在没打中狗子,狗子跑回来了。
刚开始狗子并没发怒,看熊子过来,开始围着自己转圈跑,熊孩子就在后面追,追了一会儿熊孩子看追不上,捡起石头来继续扔狗子。这时候狗子明显怒了,停下来朝着熊孩子龇牙咧嘴,眼看如果熊孩子敢再扔的话,狗子就扑上去了。我只好喝退狗子,把熊孩子也训了一顿,让他赶紧走远点。
回去的时候,也不想再见到那个熊孩子,就直接带着他们下到了沟里,顺便弄了几根杨树条给他们扭了一个哨子。
村里但凡能种树的地方,都种满了杨树。自从没人种地之后,原本经常走的一些小路也就没了踪迹,只能沿着沟底前进。
原本在路上看到几株野果的乔木,想扒出来带回家种下去的,因为没走回头路也未能如愿。现在都开花了,可能哪怕带回家了也不容易成活吧。
夜晚总是如期而至,从来都不会迟到。吃完晚饭也就该回县城了,天黑之后,路况反而没那么拥堵了。
刚开始以为是车贴的膜太黑了,右侧总是看不清楚。路上别到一辆大本,过了一会儿,从右侧超了上来,打开窗户,超我一通比划,可能还有问候吧。不过我没开窗户,一句都没听到。大哥笔画半天之后,超到了自己前面,既然是自己做得不对。那就认怂认骂,老老实实的跟在后面。等到了一个右侧的岔路口,大哥先打了个右转向,又开了双闪。还以为这无牌大哥要停车跟自己干架呢,不过自己超过去之后,对方也没什么反应,应该是对方到了目的地了。停车之后,才发现右侧车床应该是自己用湿巾擦玻璃上的鸟屎的时候,没擦干净,反而抹的那一块更加不清楚了。
把身上的衣服换下来,全部扔到洗衣机,让宝子去洗澡。衣服上占满了猫毛,狗毛,粘了半天也没粘干净,最后直接扔洗衣机给洗了。而至于鞋子,只能等回家之后再洗了。洗衣店的会员卡,基本都用来洗鞋了,价格也挺合适的9.9一双。
最后一天,八点多宝子还没醒。过去把她叫醒,洗刷吃饭,开始往回赶,毕竟作业没写完,下午还有网球课。两天疯玩,体力消耗也蛮厉害的。只是玩的时候从来都不会觉得累吧。
有时候真的羡慕这样的童年,有陪自己一起折腾的父母,也有长时间的陪伴。只是,现在,还是得为了工作绞尽脑汁,甚至有可能哪一天依然需要背井离乡。
人间四月天,总是生机勃勃,至于明天不确定的事情,明天再说吧。
2026-04-06 18:05:11
很久之前,就经常收到akismet的授权提醒,对应一个错误码10010。
刚开始还以为是多域名访问导致的授权校验出问题了。后来换了n个key,同时添加了插件hook掉所有的垃圾评论检测逻辑,让全部走统一的域名,结果前几天又收到这个提醒了。
插件代码:
<?php
/**
* Plugin Name: Akismet 单一主域名(多域名站点)
* Description: 当站点配置了多个域名时,强制发往 Akismet 的请求只使用一个主域名,避免被计为多站点触发 10010。
* Version: 1.0
* Author: obaby
*
* 使用:在下方设置 AKISMET_CANONICAL_HOME 为主域名(或留空则用 WordPress「设置」里的站点地址)。
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* 主域名(规范 URL,不要末尾斜杠)。留空则使用 get_option( 'home' )。
* 例如: https://www.example.com
*/
if ( ! defined( 'AKISMET_CANONICAL_HOME' ) ) {
define( 'AKISMET_CANONICAL_HOME', 'https://zhongxiaojie.cn' );
}
/**
* 获取发往 Akismet 时使用的唯一主域名 URL。
*/
function akismet_single_domain_get_canonical_home() {
$home = AKISMET_CANONICAL_HOME;
if ( $home === '' || $home === null ) {
$home = get_option( 'home' );
}
return untrailingslashit( $home );
}
/**
* 将任意 URL 替换为使用主域名的版本(只改 host,保留 path/query)。
*/
function akismet_single_domain_normalize_url( $url, $canonical_home ) {
if ( empty( $url ) || ! is_string( $url ) ) {
return $url;
}
$parsed = wp_parse_url( $url );
$canon = wp_parse_url( $canonical_home );
if ( empty( $canon['scheme'] ) || empty( $canon['host'] ) ) {
return $url;
}
$scheme = isset( $parsed['scheme'] ) ? $parsed['scheme'] : $canon['scheme'];
$host = $canon['host'];
$path = isset( $parsed['path'] ) ? $parsed['path'] : '/';
$query = isset( $parsed['query'] ) ? '?' . $parsed['query'] : '';
$frag = isset( $parsed['fragment'] ) ? '#' . $parsed['fragment'] : '';
return $scheme . '://' . $host . $path . $query . $frag;
}
/**
* 统一 verify-key / get-subscription / get-stats 的 blog 为主域名。
*/
add_filter( 'akismet_request_args', function ( $request_args, $path ) {
$paths = array( 'verify-key', 'get-subscription', 'get-stats' );
if ( ! in_array( $path, $paths, true ) ) {
return $request_args;
}
$canon = akismet_single_domain_get_canonical_home();
if ( ! empty( $request_args['blog'] ) ) {
$request_args['blog'] = $canon;
}
return $request_args;
}, 10, 2 );
/**
* 统一 comment-check(以及 recheck)的 blog、permalink,并把请求里的 HTTP_HOST 等改为主域名。
*/
add_filter( 'akismet_request_args', function ( $request_args, $path ) {
if ( $path !== 'comment-check' ) {
return $request_args;
}
$canon = akismet_single_domain_get_canonical_home();
$parsed = wp_parse_url( $canon );
if ( empty( $parsed['host'] ) ) {
return $request_args;
}
$canon_host = $parsed['host'];
$request_args['blog'] = $canon;
if ( ! empty( $request_args['permalink'] ) ) {
$request_args['permalink'] = akismet_single_domain_normalize_url( $request_args['permalink'], $canon );
}
// 让服务端看到的“当前请求”也统一为主域名,减少被计为多站点
if ( isset( $request_args['HTTP_HOST'] ) ) {
$request_args['HTTP_HOST'] = $canon_host;
}
if ( isset( $request_args['REQUEST_URI'] ) ) {
$uri = $request_args['REQUEST_URI'];
$request_args['REQUEST_URI'] = ( is_string( $uri ) && ( $p = wp_parse_url( $uri, PHP_URL_PATH ) ) !== null ) ? $p : '/';
}
if ( isset( $request_args['DOCUMENT_URI'] ) ) {
$uri = $request_args['DOCUMENT_URI'];
$request_args['DOCUMENT_URI'] = ( is_string( $uri ) && ( $p = wp_parse_url( $uri, PHP_URL_PATH ) ) !== null ) ? $p : '/';
}
return $request_args;
}, 10, 2 );
/**
* 统一 submit-spam / submit-ham 的 blog、permalink。
*/
add_filter( 'akismet_request_args', function ( $request_args, $path ) {
if ( ! in_array( $path, array( 'submit-spam', 'submit-ham' ), true ) ) {
return $request_args;
}
$canon = akismet_single_domain_get_canonical_home();
$request_args['blog'] = $canon;
if ( ! empty( $request_args['permalink'] ) ) {
$request_args['permalink'] = akismet_single_domain_normalize_url( $request_args['permalink'], $canon );
}
return $request_args;
}, 10, 2 );
这次授权的密钥撑得时间稍微长了点,但是最终还是收到了这个提醒,意思是需要订购商业版授权。我这个人站点为了发垃圾评论订购一个商业版授权,确实有些难以接受。
于是,我决定自建反垃圾评论系统,基于scikit-learn实现了现在的这套垃圾评论检测系统,训练数据一部分来源于github的开源数据,另外一个就是我自己博客的评论数据。为了保证样本正例和负例数量差别不至于过大,经过各种方式进行了多轮数据清洗。
如果想要评论识别更加准确,可以提供自己的博客评论数据,如果能提供垃圾评论更好。现在欠缺的主要是垃圾评论数据,正常的评论数据我已经提供几千条数据。
效果测试:
测试地址:https://anti-spam.zhongxiaojie.cn/test/spam
简介:
面向 中英混合 评论的 WordPress 垃圾识别方案:PHP 插件在评论入库前调用 本机 Python 服务,由小型多语种向量模型 + 分类器(或演示用规则)给出垃圾概率。
适合评论量不大、单机部署(例如 4 核 / 8GB RAM 的 Ubuntu),服务与 WordPress 同机时使用 127.0.0.1 即可。
目录结构:
baby_anti_spam/
├── README.md
├── screenshots/ # 文档:服务启动与 curl 自测示意
│ ├── service.png
│ └── test.png
├── service/ # Python FastAPI 侧车服务
│ ├── .env.example
│ ├── requirements.txt
│ ├── requirements-ml.txt
│ ├── run.py
│ ├── app/
│ │ └── stats_backends/ # 统计存储:sqlite / mysql
│ └── scripts/
│ ├── init_stats_mysql.sql
│ └── init_stats_mysql.py
│ ├── train_sklearn.py
│ ├── download_embedding_model.py
│ └── download_embedding_model.sh
└── wordpress/baby-anti-spam/
└── baby-anti-spam.php # WordPress 插件
关键配置:
| 变量 | 说明 | |------|------| | `SPAM_HOST` | 监听地址,同机建议 `127.0.0.1` | | `SPAM_PORT` | 端口,默认 `8765` | | `SPAM_API_SECRET` | **单密钥模式(兼容旧版)**:未配置 `SPAM_API_KEYS` 且未配置 `SPAM_API_KEYS_FILE` 时,仅此密钥有效,等价于 name=`default`、不限流(`max_rpm=0`)。与 WP 插件里填写的密钥一致 | | `SPAM_API_KEYS` | **多密钥**:JSON 数组。每项为 `name`(唯一,用于统计与限流分组)、`key` 或 `secret`(与请求头一致)、`max_rpm` 或 `rpm`(每分钟最大请求数,`0` 表示不限制)。与 `SPAM_API_KEYS_FILE` 合并时:**先读文件条目,再追加本变量** | | `SPAM_API_KEYS_FILE` | 可选,指向 JSON 文件,根节点为与上表相同结构的**数组**。文件必须存在,否则进程启动失败 | | `SPAM_MODEL_PATH` | 训练得到的 `*.joblib` 路径;留空则取决于 `SPAM_FALLBACK_RULES` | | `SPAM_FALLBACK_RULES` | 无模型文件时是否启用内置极简规则(演示用);生产训练后应设为 `false` 并配置 `SPAM_MODEL_PATH` | | `SPAM_LABEL_THRESHOLD` | 可选,默认 `0.8`。`spam_score` ≥ 此值时 JSON 中 `label` 为 `spam`,否则为 `normal` | | `SPAM_DFA_ENABLED` | 默认 `true`。为 `true` 时使用 `dfa-python-filter/keywords` 做敏感词检测;命中则直接 `spam_score=1`、`detail=dfa_sensitive`(早于 sklearn) | | `SPAM_DFA_KEYWORDS_PATH` | 可选,自定义敏感词文件路径;留空则用 `service/dfa-python-filter/keywords` | | `SPAM_NON_CHINESE_FLOOR_ENABLED` | 默认 `true`。为 `true` 时若合并后的 author/email/url/text 中**无任何 CJK 表意字符**(主要针对中文训练语料),则将 `spam_score` **至少**抬到 `SPAM_NON_CHINESE_SPAM_FLOOR` | | `SPAM_NON_CHINESE_SPAM_FLOOR` | 默认 `0.9`。与上项配合,在「无中文」评论上与 sklearn / 规则分取 `max` | | `SPAM_STATS_ENABLED` | 默认 `true`。为 `true` 时记录每次**成功**返回的 `/v1/classify` 请求与响应(失败 / 401 不落库),并允许 `/v1/mark-spam` 写入 `spam_marks` 表 | | `SPAM_STATS_BACKEND` | `sqlite`(默认)或 `mysql`。选 `mysql` 时需安装 `pymysql`(已在 `requirements.txt`)并配置下方 MySQL 变量 | | `SPAM_STATS_DB_PATH` | 仅 `sqlite`:数据库文件路径;留空则为 `service/data/stats.sqlite`(已加入 `.gitignore`) | | `SPAM_STATS_MYSQL_HOST` / `SPAM_STATS_MYSQL_PORT` | 仅 `mysql`:默认 `127.0.0.1` / `3306` | | `SPAM_STATS_MYSQL_USER` / `SPAM_STATS_MYSQL_PASSWORD` | 仅 `mysql`:连接账号(`user` 必填) | | `SPAM_STATS_MYSQL_DATABASE` | 仅 `mysql`:库名(必填),默认示例 `baby_spam_stats` | | `SPAM_STATS_MYSQL_CHARSET` | 仅 `mysql`:默认 `utf8mb4` |
系统服务启动截图:
wp插件配置:
项目地址:https://anti-spam.zhongxiaojie.cn
代码地址:https://cnb.cool/oba.by/baby-wp-anti-spam
说明:如果自己不想训练数据,下载发布版的spam_pipeline.joblib 放入指定目录下配置服务启动即可,baby-anti-spam.zip 为wp插件。
训练耗时大约11分钟:
2026-04-02 16:46:14
作为一个专业的程序媛,前端时间折腾龙虾转发公众号的文章到闺蜜圈wiki,之前已经处理了图片和文章的问题,今天转发的时候发现另外一个问题:文章里面的视频无法正常播放。
刚开始的时候想着直接去chrome的缓存里面找,但是试了下chrome://cache发现无效,又不想去找插件来干这件事情。直接去调试工具找对应的视频地址:
然而直接贴到地址栏,直接报403了。
唉,好尴尬,既然有本地缓存文件了。那么直接尝试将接收到的数据流写入到文件呗。找了半天没发现怎么直接把请求到的数据写入到文件,点击开始播放等待缓冲结束。
加载完了右下角的数据也就有了,直接切换成base64,复制粘贴:
然而,尝试decode 之后,播放不了,缺少mp4的头文件,这就挺奇怪的。文件头哪里去了?my_video为通过代码下载的mp4,video为通过base64 处理的图片。
文章测试地址:https://mp.weixin.qq.com/s/heoer_zm4SFwFKsk4tRecQ
看了下是video标签实现的:
<div data-v-c66e8e28="" class="js_inner inner not_fullscreen"><div data-v-c66e8e28="" class="js_video_poster video_poster"><div data-v-c66e8e28="" class="video_mask"></div><video data-v-c66e8e28="" src="https://mpvideo.qpic.cn/0bc3pidsgaahauamxiglsruvo6wden5aoiya.f10002.mp4?dis_k=247900efb8791f0718998ea0813793c9&dis_t=1775118363&play_scene=10120&auth_info=d9/5u/dlYUBWn6qY0Sp2SXM9PUdEOj5CZmQ3H2k2TzNOXXtjTwYQen0+WTMXEzdWIDNuS0hkIHgTMSlENWAcfUpBcQ==&auth_key=ed4a91866522f27b4b89c5e71e04d115&vid=wxv_4453415887525888005&format_id=10002&support_redirect=0&mmversion=false" poster="http://mmbiz.qpic.cn/sz_mmbiz_jpg/GAVxEAgJstytcf0uF3dpdZKia9G96C3loxCNaBrbFLHCiak3GvJDfASC7uYqNjjAZ5e2OHSmHoBQrONRJ8UIq6icJjjFXMfUBtdhy7VWlfb3MM/0?wx_fmt=jpeg&wxfrom=16" webkit-playsinline="isiPhoneShowPlaysinline" playsinline="isiPhoneShowPlaysinline" preload="metadata" crossorigin="anonymous" controlslist="nodownload" class="" style="display: block; width: 655px; height: 492px;"> 您的浏览器不支持 video 标签 </video></div><div data-v-f4ee5450="" data-v-c66e8e28="" class="video_poster__info__play" style="display: none;"><i data-v-f4ee5450="" data-v-c66e8e28="" class=""></i></div><div data-v-f4ee5450="" data-v-c66e8e28="" class="video_poster__info" style="display: none;"><p data-v-f4ee5450="" data-v-c66e8e28="" class="video_poster__info__title" style="font-size: 17px;">继续观看</p><p data-v-f4ee5450="" data-v-c66e8e28="" class="video_poster__info__desc" style="font-size: 12px;"> 孤独症,就是不爱说话吗? </p></div><div data-v-f4ee5450="" data-v-c66e8e28="" class="video_poster__info__mask" style="width: 100%; display: none;"></div></div>
还是说着这个东西还有另外的处理逻辑?哪位大神知道原因还望不吝赐教。
既然decode不行,那就直接上代码吧:
#!/usr/bin/env python3
"""
下载 mpvideo.qpic.cn 等需 Referer 的 MP4(微信视频 CDN)。
Author: obaby
https://zhongxiaojie.cn
https://oba.by
"""
import argparse
import sys
import urllib.error
import urllib.request
# 与常见微信内嵌页一致,避免 403
DEFAULT_REFERER = "https://mp.weixin.qq.com/"
DEFAULT_UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 "
"MicroMessenger/7.0.20"
)
def main() -> None:
p = argparse.ArgumentParser(description="带 Referer 下载 mpvideo MP4")
p.add_argument("url", help="完整 mp4 URL(含查询参数)")
p.add_argument(
"-o",
"--output",
default="downloaded.mp4",
help="保存路径(默认 downloaded.mp4)",
)
p.add_argument("--referer", default=DEFAULT_REFERER, help="Referer 头")
p.add_argument("--user-agent", default=DEFAULT_UA, help="User-Agent")
args = p.parse_args()
req = urllib.request.Request(
args.url,
headers={
"User-Agent": args.user_agent,
"Referer": args.referer,
},
method="GET",
)
try:
with urllib.request.urlopen(req, timeout=120) as resp:
data = resp.read()
except urllib.error.HTTPError as e:
print(f"HTTP {e.code}: {e.reason}", file=sys.stderr)
sys.exit(1)
out = open(args.output, "wb") if args.output != "-" else sys.stdout.buffer
try:
out.write(data)
finally:
if out is not sys.stdout.buffer:
out.close()
print(f"已写入 {args.output},{len(data)} 字节")
if len(data) >= 8 and data[4:8] == b"ftyp":
print("魔数检测:疑似标准 MP4(含 ftyp)")
if __name__ == "__main__":
main()
现在就可以下载之后,上传了,发布的文章地址:
2026-04-01 11:18:37
其实网站被镜像这件事情,本身没什么稀奇的,如果想搭建一个镜像网站,从零开始也不过个吧小时的时间。
之所以写这个东西,是因为最近有看到好几个人被镜像的,这一个(爱娃子),还有 这一个(我是军爸)。
不过,既然还有人有疑惑,那就简单的教一下大家怎么来镜像个网站吧。
为此,我创建了一个开源项目:
基于 OpenResty 反向代理上游站点,对 HTML 正文 做 OpenCC 简繁转换(默认:简体 → 繁体,配置文件为 s2t.json)。适合在不改源站的情况下,为访客提供另一种字体习惯版本。
| 能力 | 说明 |
|---|---|
| 反向代理 | HTTPS 回源(示例站点:zhongxiaojie.cn),客户端走本机证书与域名。 |
| HTML 简繁转换 | 仅当 Content-Type 含 text/html 时对整页做 OpenCC UTF-8 转换。 |
| gzip 解压 | 通过 Lua zlib 尝试解压响应体(与去掉 Content-Encoding 的配合视上游行为而定)。 |
| 链接与图片 URL 保护 | 转换前将 href / src / poster / data-src / srcset 及裸 http(s):// 链接替换为占位符,转换后还原,避免路径或查询串中的汉字被改写导致 404。 |
| IPv4 优先解析 |
resolver … ipv6=off + 变量 proxy_pass,减轻云主机无 IPv6 时对 AAAA 连接失败的问题。 |
| 静态资源直过 | 图片、CSS、JS、字体等扩展名单独 location,不做 OpenCC,减轻负担、避免误伤二进制。 |
| 动态库加载 | 对 libopencc.so 按常见路径依次尝试 ffi.load,降低找不到共享库的概率。 |
style="background:url(...)" 未单独做保护,若遇少数破图可再扩展规则。nginx/opencc/opencc-filter.lua 中的 OPENCC_CONFIG(默认 /usr/share/opencc/s2t.json);若需 繁体 → 简体 可改为 t2s.json 等(需系统已安装对应 OpenCC 数据文件)。lua-nginx-module)。libopencc.so 与词典数据(如 /usr/share/opencc/*.json),并保证 worker 进程能加载到 .so(见下文「共享库」)。require('zlib') 的模块(用于 zlib.inflate,若无 gzip 体则 pcall 失败会跳过解压,不影响后续逻辑)。resolver 时 VARIABLE 形式 proxy_pass 才会走指定 resolver)。以 Debian / Ubuntu 为例(包名因发行版略有差异):
sudo apt update sudo apt install -y libopencc1.1 opencc # 或 libopencc2 等,以仓库为准 或者手工复制 lib64目录下的文件到 脚本对应的路径就是这个 /usr/lib64
确认存在词典,例如:
ls /usr/share/opencc/s2t.json
libopencc.so
若日志出现 libopencc.so: cannot open shared object file:
ldconfig -p | grep opencc
/usr/lib64 等非默认路径,可执行(与仓库 fix.md 一致):echo '/usr/lib64' | sudo tee /etc/ld.so.conf.d/usr-lib64.conf sudo ldconfig
Environment="LD_LIBRARY_PATH=/usr/lib64:/usr/local/lib" 后重启。脚本内已对多路径做了 ffi.load 尝试;仍失败时请对照 ldd 与 opencc 包实际安装位置排查。
将 nginx/opencc/opencc-filter.lua 复制到服务端约定路径(与 nginx 配置一致),例如:
sudo mkdir -p /usr/local/openresty/lua sudo cp nginx/opencc/opencc-filter.lua /usr/local/openresty/lua/opencc-filter.lua
按需修改脚本顶部 OPENCC_CONFIG 指向本机实际的 JSON 配置。
zero.zhongxiaojie.cn.conf 中的 server 块纳入主配置(include 或粘贴到 nginx.conf 的 http {} 下)。zhongxiaojie.cn、以及 body_filter_by_lua_file 的路径,使其与当前环境一致。header_filter_by_lua 中去除 Content-Encoding,便于对明文 HTML 做处理;若上游与解压逻辑不匹配,需自行观察是否需要调整。sudo /usr/local/openresty/nginx/sbin/nginx -t sudo /usr/local/openresty/nginx/sbin/nginx -s reload # 或 systemctl reload openresty
% 编码的路径)。error.log 中不应再出现 OpenCC 库加载失败或大量 IPv6 unreachable(在无 IPv6 环境)。| 项目 | 位置 |
|---|---|
| OpenCC 配置 JSON |
opencc-filter.lua → OPENCC_CONFIG
|
| Lua 脚本路径 |
zero.zhongxiaojie.cn.conf → body_filter_by_lua_file
|
| 上游站点 |
set $upstream_host … 与 proxy_pass https://$upstream_host$request_uri
|
| DNS / 仅 IPv4 | resolver 223.5.5.5 8.8.8.8 valid=300s ipv6=off |
| 不参与转换的静态文件 | `location ~* .(gif |
| 现象 | 可能原因 |
|---|---|
libopencc.so 找不到 |
未安装包、ldconfig 未包含库目录,或需 LD_LIBRARY_PATH
|
body_filter 报错、栈指向 ffi.load
|
同上;或架构不一致(如 32/64 位混用) |
| 上游连接 IPv6 失败 | 已用 ipv6=off + 变量 proxy_pass;仍失败则检查防火墙与 DNS |
| 图片 404 | 历史上多为 OpenCC 改了 URL 内汉字;当前脚本对常见属性已做保护,若仍有个别,检查是否来自 CSS url() 或 JS 动态拼接 |
如需改为其他域名、证书路径或 t2s 转换方向,只需改配置文件与 OPENCC_CONFIG,无需改 OpenResty 核心。
实际效果:
开源项目地址:https://gitee.com/obaby/baby-website-mirroring-tool
参考链接:https://blog.csdn.net/wzj_110/article/details/127758020
https://blog.rexskz.info/support-traditional-chinese-using-openresty-and-opencc.html
2026-03-30 11:00:34
春天到了,又到了万物复苏的季节。小草也从土里钻出了头,露出了点点的绿色。树上的花也开始绽放,虽然没有绿叶的衬托,倒是也别有一番韵味。
每个周末,都大同小异。跟上班一样,也没有什么特别的,与渐渐生机勃勃的春天比起来,似乎有些过于平淡了。每周依然是带着宝子上课,上课,剩下偶尔有那么一点时间,也不能走的太远,只能周边小范围的溜达溜达。
宝子现在上课不太需要自己去帮忙了,至少能隔网之后,不用自己跟着去抛球了。这样就有点时间可以继续跑步了,围着网球场一圈一圈的跑。
不过相比操场的400米跑道,这一圈一百多米跑起来感觉更累,感觉跟拉磨的驴一样。
周日上午的网球课推迟到了十一点,早上早点起来,宝子嚷嚷着去公园,我嚷嚷着去轮渡。当然,公园是上午去,轮渡是下午去。对象再次征求宝子的意见,得到的答复是两个都去。
急忙吃完东西,开车去公园。上车之后发现前挡风玻璃上面有点雨点,想着是不是要下雨,跟宝子说,下雨了可能。她表示怀疑,没感觉到雨点啊。不过既然没感觉到,那就去公园吧,一路上虽然天依然阴沉,刚停好车,拉开车门的瞬间,一阵大雨落了下来,赶忙重新钻进车里。前后不到五秒中的时间,就让这场公园之旅化为了泡影。
回家之后,宝子在家换上轮滑鞋,来回溜达。想着,既然下雨了,应该网球课也停了吧,然而等到十点半依然没消息。既然如此,那可能市北没有下雨吧。一路上天变得越来越晴朗,到了学校之后依然是一篇艳阳天。
下课去学校餐厅吃点东西,下午就可以去轮渡了。
虽然是中午的饭店,想找个空位置还是蛮方便的,买了两碗麻辣烫,没有小碗。去隔壁的卤肉饭要了个小碗。餐厅门口从外面能感受到吹进来的真真的凉风。吃完饭,到了室外反而变得更加的温暖,与餐厅的温度完全不同。
一路小道辗转到环湾大道,路况还是比较畅通的,半个小时就到了轮渡停车场。怕找不到停车位,在入口找了个空塞了进入。往里走,大约100米发现内部竟然有一个大的停车场,并且还有大量的空位。
候船厅入口,写了一些方言的标语。
虽然购票的时候显示有场次,但是实际买的时候,就是看人数,人够了就发船。感觉到了县城做镇上的客车的感觉,流水车,流水船。检票的时候会给一个油条,这个油条并不是给游客吃的,是给海鸥吃的。
这个码头,在海底隧道和跨海大桥开通之后就已经停了好几年,这两年才又重新开放,用来做海上观光。成人票两个人90,宝子2块,价格还是蛮合适的,海上50分钟的观光,说可以看到跨海大桥。
检票上船,刚找地方坐下,就有工作人员开始喊,大家可以去喂海鸥了。喂海鸥只能在一楼,二楼是观光区域,油条投喂完之后可以上二楼。带着宝子来到船尾,大家都在那里观望,也不知道在等什么,我扔了一块油条出去之后,马山就有无数的海鸥飞过来开始争抢。大家也跟着开始往海里投喂。
发船之后,海鸥依然跟着游船,甚至能感受到海鸥翅膀煽动的时候,甩出来的水滴。
还有跟着游船一直来回盘旋,不断的争抢从游客手里抛出的油条。
当然啦,如果足够大胆,也可以直接用手拿着让海鸥去吃。
不过,这天气稍微微差点了,大约半小时不到,就开始掉头返航了。说好的看跨海大桥呢?海面的能见度并不算高。只能隐约看到一个轮廓,远处的货轮也一样,飘渺。
回港之后,依然能看到很多人在等着检票。如果五点多应该能看到海上的日落,很多人也在等那个最好的时间。
海上的冷风吹来,单穿一条牛仔裤还是冷了点。
好在没有因为家里的一场雨而不出门,毕竟其他的地方是一片艳阳天,不过局部有雨。