跳到主要内容

大便化与薅羊毛

· 阅读需 5 分钟

科利·多克托罗(Cory Doctorow)2023年出版的这本书,全名是《The Internet Con: How to Seize the Means of Computation》(中文暂译《互联网骗局:如何夺取计算手段》)。

它的核心内容可以用一句话概括:互联网平台如何一步步变烂,以及我们普通人能怎么“拆掉”它们。

书里主要讲了这四件事:

  1. 什么是“大便化”(enshittification) 多克托罗把这个过程拆解成三个阶段,非常精准:
    第一阶段:平台讨好用户(给你免费、好用、无广告的服务,吸引你留下来)。
    第二阶段:用户成了“人质”后,平台开始虐待用户,转而讨好商家/广告商(比如提高抽成、强制投广告)。
    第三阶段:用户和商家都被锁死了,平台开始虐待所有人,把利益全归自己。
    他举的例子包括:亚马逊搜索结果变广告、谷歌搜索质量下降、Uber涨价司机收入却变少、社交媒体推给你不想看的内容……这些都是“大便化”的表现。
  2. 为什么平台敢这么干? 因为转换成本太高。你所有的数据、社交关系、内容都锁在平台里,离开的成本太高,所以平台可以为所欲为。这种“锁死”是平台变烂的根源。
  3. 怎么对抗“大便化”? 书的核心不是“抱怨”,而是“解决方案”。多克托罗提出了几条实际路径:
    🔗-强制互操作性(Interoperability):比如让用户能把自己的数据(社交关系、购物记录、评论)从一个平台搬到另一个,就像转网一样。
    🌐-支持去中心化协议:比如你可以做的——用个人网站、RSS、本地模型,而不是依赖某一家公司。
    ⚖️-用监管打破垄断:比如《数字市场法案》(DMA)这类法规,强制平台开放接口。国内则是《互联网平台反垄断合规指引》
  4. 个人能做什么? 书的给普通人的建议,其中包括:
    🌍-拥有自己的域名和网站(快行动起来🥢)。
    📡-用RSS而不是算法推荐(很爽的😄)。
    💻-支持开源软件和去中心化项目(不再担心盗版PS出海被告,对不起暂时不行)。

“薅羊毛”和“Enshittification”,恰好是同一枚硬币的两面。 平台那面叫“Enshittification”:平台在设计一套规则,让用户和商家在不知不觉中让渡更多利益。 用户这面叫“薅羊毛”:既然规则是这么设计的,那我就在规则允许的范围内,找对自己最有利的玩法。

我们已经被训练成一种“博弈心态” 不再期待平台是“好人”,而是默认它一定会“使坏”。 所以我们的行为也变了:“能薅就薅,薅不到就换,换不了就忍”。这变成了一种生存策略。

这种“默认常态”是怎么形成的?
-早期互联网:是一种“田园时代”,大家默认平台是工具、是朋友。
-平台成熟后:用户逐渐发现,自己不是“用户”,而是“产品”或“资源”。
-现在的状态:双方都心知肚明——平台在算计用户,用户也在算计平台。信任感被磨损掉了,剩下的是纯粹的博弈关系。


其实我们在吃饭层面也体验过不止一遍

大陆互联网用户对“平台大战”的记忆,比港澳、台湾地区和欧美用户要深刻得多,也直接塑造了我们对平台的集体心态。

我们算是被教育过不止一次了,2010年代初那场惨烈的“千团大战”和之后的补贴大战。那是大陆互联网特有的“饱和式竞争”:
-🎟️千团大战:几千家团购网站同时存在,疯狂烧钱补贴,最后只剩美团一家。用户第一次体验到“平台也会死,补贴也会停”。
-🚕打车大战:滴滴和快的补贴打到白热化,用户用几块钱甚至免费打车。合并后补贴消失,价格涨回来。
-🍱外卖大战:美团、饿了么、百度外卖补贴打到用户“吃外卖比自己做还便宜”。合并后,配送费、打包费开始悄悄加上去。
-🚲共享单车大战:ofo、摩拜、哈啰等疯狂投放,押金退款难、车辆成垃圾山。用户押金被套牢,第一次体验到“平台也会跑路”。
(引用自我本人,没错,我说的。)

这些“大战”有一个共同模式: 先用补贴培养用户习惯(用户觉得“这平台真好”)。 然后合并/淘汰(只剩一两家)。 最后“收割”(涨价、降质、加规则)。 所以,大陆用户被训练出了一种“生存智慧” “薅羊毛”心态:我们知道补贴不会持续,所以趁有就赶紧薅。 “不信任”心态:我们知道平台最终会变差,所以不做长期绑定。(或许吧,事实上也没其他选择了👋)


所以,行有余力的情况下,架设一个自己的网站很有必要,尤其在起步阶段,弄个hugo(不要害怕,这在2026今天随便问那个ai大模型都能教你安装,除了豆包,学写markdown大概能用一辈子。

诚实地告诉你:这个文章很多材料都是我从ai摘录的,不过每一句都是我想说的😄

饮食几则,两则

· 阅读需 1 分钟

煮燶飯

是日作書,人機交互,字字斟酌,釜鑊沸炭,是為燶飯。焦苦繞樑,反覆穿梭,門戶大開,不得消散。飯苦否?還好吧😅

酸瓜赋

酸瓜還冒泡,起落膠瓶中。瓜片並皮芽,青綠化玉黃。童子不愛食,老子如壽膏。今夜多作弄,明月又得嘗。

七月记上半

· 阅读需 2 分钟

七月过半,看看自己这半个月做了什么👀

blogblog

写了好多blog,一天平均两篇,半个月十五篇其实挺吓人,虽然很多是废话,但都有自己地盘了,说废话也挺好。 也给blogblog同乐会写了一篇,不过暂时没有收录就是了(可能是我的笑话回路太高级😄)

技术面向

学了好几种md语法,比如别理我快来看
我很聪明谦虚。 还有’这个’和 那个(模糊)

RSS

我在本地部署了FreshRss,对于新手来说算是很厉害,成就感满满了👍 接下来可以再搞FFfemp(先休息一下) 接入了几个rss,看看我之后增加blogroll页面的时候会放进去。

绘画

本职工作在这个月反而放下了,就画了几张,随着blog和本地工具部署好,可以更好投入图像创作了。

音乐

目前还是直录,没有时间精力搞环境,stable audio3也因为显卡驱动太旧用不了,不过不是优先项。

另外的

有点想手搓html做个y2k或者80s dos and crt风格的网页 —-

估计这个月是我近十年时间体感经过最快的一个月了,或者这是充实的开始😄

人间滋味🍗

· 阅读需 3 分钟

夜里失眠,读汪曾祺《人间滋味》,形成记忆。

结构如下:

食材或食物

一顿瞎吹有感而发

食材或食物

一顿瞎吹有感而发 ……

早上起床,饮早茶,直落,排便,梳洗,有所灵光,仿作如下。(排名不分先后)

-腐皮卷: 馅料随便,猪肉、虾仁、韭黄、冬菇,卷进腐皮里,煎了或蒸了,就是一味。腐皮本身味道不重,但它能吸汁,能把别人的味道拿来,变成自己的。北冥神功,吸星大法,跟我一样。

-叉烧酥: 顾名思义,叉烧包裹上油酥,刷上蜜汁。烘烤,其实是西饼costa。酱叉烧可以用于此酥也能作叉烧包,油酥是改良的现代饼酥,好吃,有层次,有咬口。经典的猪油酥太粉,我不爱吃。

-艇仔粥: 艇仔粥从前是在小艇上卖的,珠江边,夜里,一碗粥,几片鱼,一把花生。现在艇仔粥不在艇上了,在茶楼里,但名字还在。粥底要绵,绵米粥,毋米粥。配料有鱼片、鱿鱼、猪肚、花生、油条,还可以有叉烧丝和烧鸭碎,加上这两个更好吃。粥这东西,是最民主的食物——什么东西都可以往里放,放进去都不违和。

-鸭扎: 鸭扎这东西,边角料,好好味。鸭肠、鸭肝、鸭心、芋条,拿鸭肠扎成一捆,蒸熟,浇上酱汁。看起来像是没时间做饭的人随便捆的,吃起来却层层叠叠,从肠的韧到肝的嫩,再到芋头的粉糯,水调歌头,渔歌晚唱。 亦有其他变种的扎,鸡扎,鱼扎,花扎,人扎(你要是愿意的话)

-猪肝烧卖: 北方的烧卖包羊肉,穷酸点就包饭。广州的烧卖包猪肉,但猪肝烧卖是个异类。一片猪肝盖在肉馅上,蒸出来,猪肝是粉的,肉馅是弹的,两口并作一口,哪怕蒸过头了,蒸到肝硬化了,也还是好吃。

-糖沙翁: 糖沙翁这东西,像是炸过的泡芙,又像是甜的油条,总之是不中不西、不伦不类。但好吃。外层是糖霜,咬下去沙沙响,里层是蛋糊,绵软得像云。据说它是从西洋“Soufflé”来的,到了广东,改头换面,落地生根,变成了早茶桌上最不起眼的甜点。没人会专门为糖沙翁去喝茶,但它要是没有了,又觉得少了点什么。

牛肉丸、虾饺、皮蛋酥、牛仔骨,这几样我不打算写了。

纠结、绕远路、黑洞效应

· 阅读需 2 分钟

收敛一点,正经一些。 我其实很容易进入一种过度内省的状态,说一句话我会跟自己辩论六次。你或许会说:这样不累吗?我也觉得,不过这是被动技能,关闭不能。 一个外部情景接入我的感受终端的时候通常亦会无限反射(你们看,我又说怪话了)。

或者说,我这类情况叫很会斟酌。 来个很硬的转折,我来讲讲纠结:纠结 gau² git³,汉语词汇,本来习惯上应该读纠结 jiū jié ,形容人非常地纠结。你看,这就是纠结。

这就会导致我常在怀疑自己是不是’ 绕远路’以及’怀疑怀疑自己绕远路‘,如下情况: 我今日(或许是前天,总之是某一天)用三小时完成了FreshRss的安装,中间因为缺乏相关知识而绕了不少远路,比如没意识到可以用docker而转用php方式安装,无知而被ai戏耍,这已经不是我第一次被ai戏耍了,估计未来还有无数次。

你看,这就是绕远路。然后我还会复盘为什么没有发现更好更高效的方式呢?最后,当然会被我的大脑消解成:这不倒很正常吗?你盲开地图喔😯多做多积累就会了。

然后又思考为什么我怀疑自己。你看,这就是怀疑自己绕远路。多么纠结。

黑洞效应则是另一种情况,跟前面说的有关系,但不是全部关系。 ……

欢迎来到RSS

· 阅读需 3 分钟

没错,我也要跟绝大部分bloger和互联网复兴运动guy一样安利RSS,话术也无外乎是,RSS更懂你,过滤垃圾社交媒体,重夺注意力之类的话。1

那么,什么是rss,为什么用rss?

在2026年的今天,几乎所有的社交平台都不尽其用地要把你留在平台里面,而且绝大部分内容都是垃圾信息,看多了就会被塑造得很聪明变弱智,我当然不是说rss订阅的网站博客都是好东西,有不少也是牛鬼蛇神

但最核心的好处是,我们可以自己选择不用被喂屎,这样发现深度连结的内容,才能成为可能(比如我)😊

什么是rss?

rss是个比社交平台还古早的技术,由网景公司在1999年开发……(我不想听🤚)

简单说它就是主动阅读器,把你自己喜欢的网络信息集成到一个阅读器上,喜欢就留着不喜欢就删掉。

比如把这串神秘代码复制进你的订阅器窗口:

https://pennameco.xyz/blog/rss.xml

(它就会在你的阅读器上显示出来) rss

有什么订阅器选择?

我完全建议直接用Feedly 这种注册马上能用的订阅器,不过境内网络环境需要科学上网很难连上去,而且还有小广告(虽然不多),其实搞起来也很烦。

那不如一步到位,用FreshRSS直接安装到自己的电脑本地💻,在浏览器就能打开。不需要害怕什么难度,上官网下载源码,然后让ai告诉我们怎么弄就行。

(FreshRSS支持深色模式,无需多言😊)

我的RSS漫游

不要看我这安利一大轮,我也只是用了几天(现学现卖),但是自从过渡到RSS之后,人精神了,头不痛了,不感冒了。生活都疏朗了起来😄 —- 好了,我的废话讲完了。其实以下blog都讲得比我好,我的观点和结构也是 Ctrl + C 复制他们的,不过这不重要:

你需要用 RSS,不要再拖了
不滑社群媒體還有短影音還可以幹嘛?

起码不用看广告就很爽不是吗?我就是想炫耀一下我新学会的几种语法

烦恼图片压缩📦

· 阅读需 5 分钟

看看我的画作页面,每次我打包我美好的作品,然后批量压缩的时候,我都挺可惜的。 压缩过的图片会很不精神,细节难免丢失。
如果没看过原图,那确实没什么关系,但是看过原图对比起来,诸如锐度下降,纯度下降,微妙的透明色丢失,尺寸变小都让人沮丧。

可是为什么必须压缩呢? 因为每张图片20多mb那就根本没法读取了(我只用得起3m小水管)。对于图像艺术家真的很苦恼☹️

所以目前方法是尽量用脚本批量成webp,把尺寸锁在2k(2560x1440),质量92%。别人不知道,起码自己观感好很多,读取速度也还能接受。(或者我未来能有效优化吧)

我目前用的压缩jsx是这个👇
路径是: /my-website/scripts/compress-images.js

compress-images.js

const fs = require('fs-extra');
const path = require('path');
const sharp = require('sharp');

// ============ 配置 ============
const SKETCH_DIR = path.join(__dirname, '../static/sketch');
const BACKUP_DIR = path.join(__dirname, '../static/sketch-backup');
const MAX_WIDTH = 2560; // 最大宽度 2560px
const MAX_HEIGHT = 1440; // 最大高度 1440px
const WEBP_QUALITY = 92; // WebP 质量 92%
const WEBP_EFFORT = 6; // 压缩努力程度 0-6
// ==============================

// 支持的图片格式(会被转成 WebP)
const IMAGE_EXTS = ['.png', '.jpg', '.jpeg', '.webp', '.avif'];

// 获取文件大小
function getFileSize(filePath) {
const stats = fs.statSync(filePath);
return stats.size;
}

function formatSize(bytes) {
if (bytes < 1024) return bytes + 'B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB';
return (bytes / (1024 * 1024)).toFixed(2) + 'MB';
}

// 处理单张图片 - 转 WebP
async function processImage(inputPath, backupPath) {
// 备份(如果还没备份过)
if (!fs.existsSync(backupPath)) {
fs.ensureDirSync(path.dirname(backupPath));
fs.copyFileSync(inputPath, backupPath);
console.log(`📦 已备份: ${path.relative(process.cwd(), inputPath)}`);
}

const originalSize = getFileSize(inputPath);
const metadata = await sharp(inputPath).metadata();
const ext = path.extname(inputPath).toLowerCase();
const fileName = path.basename(inputPath, ext);
const dir = path.dirname(inputPath);

// 构建 WebP 输出路径
const webpPath = path.join(dir, fileName + '.webp');

// 开始处理
let pipeline = sharp(inputPath);

// 缩放(如果宽度或高度超标)
if (metadata.width > MAX_WIDTH || metadata.height > MAX_HEIGHT) {
pipeline = pipeline.resize({
width: MAX_WIDTH,
height: MAX_HEIGHT,
fit: 'inside',
kernel: 'lanczos3',
withoutEnlargement: true,
});
}

// 转换为 WebP
pipeline = pipeline.webp({
quality: WEBP_QUALITY,
alphaQuality: WEBP_QUALITY, // 透明通道质量
lossless: false,
effort: WEBP_EFFORT,
nearLossless: false,
smartSubsample: true, // 智能子采样
});

// 写入 WebP 文件
await pipeline.toFile(webpPath);

// 删除原图(如果不是 WebP)
if (ext !== '.webp') {
fs.unlinkSync(inputPath);
} else {
// 如果是 WebP,删除临时文件(实际上就是原文件)
// 但我们已经覆盖了,所以不用额外操作
}

const newSize = getFileSize(webpPath);
const saved = ((originalSize - newSize) / originalSize * 100).toFixed(1);
const ratio = (newSize / originalSize * 100).toFixed(1);

const fromExt = ext !== '.webp' ? ext.toUpperCase().replace('.', '') : 'WebP';
console.log(`🔄 ${fromExt} → WebP: ${path.basename(inputPath)}`);
console.log(` 📊 ${formatSize(originalSize)}${formatSize(newSize)} (${ratio}%,节省 ${saved}%)`);

return { webpPath, originalPath: inputPath };
}

// 递归扫描文件夹
async function scanDirectory(dir, backupBaseDir) {
const items = fs.readdirSync(dir);
let processed = 0;
let totalFiles = 0;
let skipped = 0;
let totalSaved = 0;
let totalOriginal = 0;
let converted = 0;

for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);

if (stat.isDirectory()) {
// 跳过备份目录
if (fullPath === BACKUP_DIR) continue;

const subBackupDir = path.join(backupBaseDir, item);
const result = await scanDirectory(fullPath, subBackupDir);
processed += result.processed;
totalFiles += result.totalFiles;
skipped += result.skipped;
totalSaved += result.totalSaved || 0;
totalOriginal += result.totalOriginal || 0;
converted += result.converted || 0;
} else {
const ext = path.extname(item).toLowerCase();
if (IMAGE_EXTS.includes(ext)) {
totalFiles++;
try {
const relativePath = path.relative(SKETCH_DIR, fullPath);
const backupPath = path.join(BACKUP_DIR, relativePath);

// 如果是 GIF,跳过
if (ext === '.gif') {
console.log(`⏭️ 跳过 GIF(保留动画): ${path.basename(fullPath)}`);
skipped++;
continue;
}

const originalSize = getFileSize(fullPath);
await processImage(fullPath, backupPath);

// 获取新文件大小
const fileName = path.basename(fullPath, ext);
const dir = path.dirname(fullPath);
const webpPath = path.join(dir, fileName + '.webp');
const newSize = getFileSize(webpPath);

totalSaved += (originalSize - newSize);
totalOriginal += originalSize;
converted++;
processed++;
} catch (err) {
console.error(`❌ 处理失败 ${item}:`, err.message);
}
} else {
skipped++;
}
}
}

return { processed, totalFiles, skipped, totalSaved, totalOriginal, converted };
}

// ============ 主程序 ============
async function main() {
console.log('🚀 开始批量转换 WebP...\n');
console.log(`📊 最大尺寸: ${MAX_WIDTH}×${MAX_HEIGHT}px`);
console.log(`📊 WebP 质量: ${WEBP_QUALITY}%`);
console.log(`📊 压缩努力度: ${WEBP_EFFORT}/6\n`);

if (!fs.existsSync(SKETCH_DIR)) {
console.error('❌ 找不到 static/sketch 目录');
process.exit(1);
}

console.log('📊 正在扫描...');
const result = await scanDirectory(SKETCH_DIR, BACKUP_DIR);

const totalMB = (result.totalOriginal / (1024 * 1024)).toFixed(1);
const savedMB = (result.totalSaved / (1024 * 1024)).toFixed(1);
const finalMB = ((result.totalOriginal - result.totalSaved) / (1024 * 1024)).toFixed(1);
const savedPercent = result.totalOriginal > 0 ? (result.totalSaved / result.totalOriginal * 100).toFixed(1) : 0;

console.log('\n' + '='.repeat(50));
console.log(`✅ 全部完成!`);
console.log(` 📁 转换了 ${result.converted} 张图片为 WebP`);
console.log(` 📁 处理了 ${result.processed} 张图片`);
console.log(` 📁 共 ${result.totalFiles} 张图片(跳过 ${result.skipped} 张)`);
console.log(` 📊 总大小: ${totalMB}MB → ${finalMB}MB`);
console.log(` 📊 节省: ${savedMB}MB (${savedPercent}%)`);
console.log(` 📁 原图备份在: ${BACKUP_DIR}`);
console.log(`\n💡 所有图片已转换为 .webp 格式!`);
console.log(`💡 如果效果不满意,可以从备份恢复:`);
console.log(` cp -r ${BACKUP_DIR}/* ${SKETCH_DIR}/`);
}

main().catch((err) => {
console.error('❌ 程序出错:', err.message);
process.exit(1);
});

在根目录运行以下终端指令,他会生成一个备份文件夹📂,你也可以做成bat或者其他脚本。

npm run compress

以下听不懂,直接把上面的脚本扔给ai改

📌 使用前注意 依赖:fs-extra 和 sharp,运行前先装:

npm install fs-extra sharp

图片默认放在 static/sketch/ 下,如果你的图片在其他地方,修改脚本开头的 SKETCH_DIR 和 BACKUP_DIR 就行。 在 package.json 的 scripts 里加一行:

"compress": "node scripts/compress-images.js"

然后 npm run compress 就能跑了(大概)。

鸡是我们最好吃的朋友🐔

· 阅读需 2 分钟
⤵ 这是这个有益身心活动的推广,他们并没有逼我写

本文参与「BlogBlog 同乐会 - 2026 年 7 月」主题「有趣的小知识或冷门概念」,主持人刘昕
如果你也有部落格且恰好看到这里,欢迎一起来玩。

基于广东人对于鸡的热爱,自然是要八卦一下跟鸡🐔有关的概念,比如以下的肉鸡:

什么是肉鸡🐔?

这是一个网络安全黑话,形容黑客入侵他人的服务器(电脑)然后进行控制和破坏,那个被操控的电脑就是肉鸡。至于为什么叫‘肉鸡’而不是‘肉鸭’,大概是因为黑客也喜欢吃鸡。

而这种现象meme化之后就会变成神经肉鸡🐔,如果你能理解这是什么发神经的蠢东西的话😄
事实上也可以玩这么一个文字游戏,x + 肉鸡 =

x结果
电子电子肉鸡
赛博赛博肉鸡
文化文化肉鸡
算法算法肉鸡
神经神经肉鸡
社畜社畜肉鸡
存在存在肉鸡
味觉味觉肉鸡
记忆记忆肉鸡
……发挥脑洞,任意版本😄

然后就可以在得到的缝合名词里面一顿胡扯。

比如“存在肉鸡”大概是这么个东西:
你觉得自己在生活,其实是被生活养殖。每天按时进食(上班)、保持清洁(洗澡)、偶尔放风(周末),看似自由,但围栏一直都在。你不知道养殖者是谁——可能是系统,可能是资本,可能只是“大家都这样”。你偶尔会抬头看看天空,然后低头继续吃。这就是存在肉鸡。

至于,为什么又写废话,大概是我很爱吃鸡🐔

鱼与鱼与鱼🐟️

· 阅读需 1 分钟

我很喜欢吃鱼,当然也很喜欢看鱼,各种各样的鱼,特别是放在蓝色的玻璃箱里面的鱼,看起来就很好吃🍗

至于养鱼,我应该不会去养的,我小时候养过,千禧年前后曾经风靡过养鱼热。 特别是广州,这里可是有全中国最大的观赏鱼养殖基地,还有花鸟鱼虫市场。

一个市场叫花鸟鱼虫市场,那得有多少的花鸟鱼虫😊

对了,说回来。 养鱼其实是一件很劳累的爱好,换水,养水生态,再到鱼与鱼,鱼与海藻,鱼与珊瑚,珊瑚与海藻,鱼与海藻与珊瑚,都有关系。

当然了,这只是我在瞎吹,可能严重复杂化了。对于喜欢的人,熟练的,其实就是养好水,日常清洁。

OK,简而言之,我就是喜欢吃鱼 鲟鱼 对了,来看看(想象一下)我最近拍摄的鱼鱼图鉴👀 !(鲟鱼) !(多宝鱼) !(生鱼) !(海鲈鱼) !(鲍鱼) !(鲸鱼) !(人鱼)

哈哈,还有什么好吃的鱼,欢迎大家告诉我。