分类
研究

针对博客网站友链的随机网络研究

关键词:博客友链网络;随机网络;小世界现象;数据爬取

一、前言

“六度分离”理论已在社交网络、互联网等领域被研究1。博客网站作为Web2.0的代表,其形成的友链网络是否可视为随机网络?小世界现象在博客网络中是否存在?这是本文想要研究的问题。

笔者依照浏览博客的经历做出假设:博客友链在一定程度上参杂了博主的个人喜好,可能在一定程度上表现出规律性,更偏向于小世界网络,无法视为随机网络,小世界现象在博客网络中存在。

本文期望对较大体量的博客网络进行研究,明晰以下问题:访客从一个博客到另一个博客平均需要经过多少个博客?每个博客平均有多少个友链?

博客是一种杂志形式的私人网络日记。博客软件自动将新条目(帖子)放在网页的顶端,每过一定的时间,或每当帖子太多不便于翻阅时,博客软件就会把旧条目放进博客档案。2友链,即友情链接,指两个网站之间架起一座桥梁,实现流量的互通。3友情链接在门户、电商网站常被用作搜索引擎优化的一种手法,也被学界认为是引入流量、提高权重的工具。但笔者认为,在博客中,友情链接在搜索引擎优化方面的作用减弱,交流、互访等社交方面的作用得到增强。“从20年前建站伊始,我的友情链接页面就不是以SEO或其他什么乱七八糟的目的而存在。”4笔者通过丑搜(一款中文博客搜索引擎)以“友情链接”为关键词搜索博客网站的友链页面,绝大部分博客5没有在友情链接页面要求网站权重。博客友链具有相互性(友链应当是两个博客之间的互链),为研究方便起见,笔者将友链规定为博客在首页和友链页面对其他博客的外链,无论其是单向的还是双向的。

爬取博客网站首先需要获取一个数据量比较大的博客列表,笔者查阅了一位博主自发收集的博客列表[4],并对列表的数据量进行统计,结果如表 1。

名称数据量
BlogFinder1699个博客6
BlogWe282个博客7
Blogroll Network Map约7900个博客8
BlogsClub368个博客9
十年之约1818个博客10
集博栈4115个博客11
表1 部分博客列表的数据信息

二、研究过程

按照博客列表收录数据的体量,笔者使用了Blogroll Network Map、BlogFinder、十年之约和集博栈的数据。对上述几个博客列表的数据进行爬取,将去重后的数据中的博客名称和博客地址存入数据库,如图1。

图1 存储博客网站信息

接着对博客网站的首页和友链页面进行爬取。在此过程中,程序通过两条途径得到友链页面的URI:在首页搜索是否有链接文本包含“友情链接”等字样的链接;尝试一些常见的友链页面的URI是否存在。

图 2 预先设置一些常见的页面URI进行穷举

若博客有链接到另一个博客的外链,则将其视为友链,将这个关系存储到数据库;若博客的外链地址不在此前存储的博客数据库中,则将链接地址存入外链数据库,方便后续步骤发现此前没有发现的博客。

在爬取博客首页和友链页面的程序运行之后,数据库中存储的外链数量快速增长。在程序进行到一半时,数据库中存储的外链已经达到了4485条(过程中已经按照外链的域名进行去重)。这已经超过了笔者能够人工处理的范围。

笔者将外链的网页标题、描述、URL、关键词、订阅链接、文章链接数量、导航结构等信息交给AI,由AI判定外链是否为博客。

图 3 传递给AI的数据

但这种方法伴随着大量的token消耗。在判断出40余个博客网站后,腾讯混元赠送的100万个token就耗尽了。面对这一问题,设置了博客网站的一些特征,例如“文章”“评论”等,程序将对外链的特征进行评分,判断外链是否为博客网站。

为了缩短程序运行的时间,程序采用多线程运行。

在程序完成博客网站的判断之后,笔者对数据库存储的博客网站进行人工抽检,如图4。人工抽检的355个网站中,有48个不是博客网站,其中大部分为域名过期。

图 4 人工抽检界面

通过程序可以绘制出博客之间的平均距离图像。

三、研究结果

由上图,我们可以看到:

  • 博客友链网络中绝大多数博客对的最短距离集中在4-6之间,小世界现象在博客友链中存在。
  • 几乎所有博客对的最短距离不超过6。
  • 最短距离的标准差较小,博客之间距离的分布波动性较弱。
  • 网络的聚类度较低,博客的友链不集中,友链“抱团”现象不明显。

由图7的度分布图可知,小度节点(博客)数量极多,大度节点数量稀少,满足幂律分布,与随机网络满足的泊松分布不同。故,博客友链网络不是随机网络。

附录与参考文献

本文的代码和原始数据均公开于GitHub仓库https://github.com/Xiaozonglin/blog-friendlink-network-research
供复现和检验。

  1. 庞景安.Web 小世界特征的网络计量学研究[J].情报科学,2007 (08):1171-1175. ↩︎
  2. J.奎根,张斌. 博客、维基和创造性革新[J]. 国外社会科学,2008,(03):98-104. ↩︎
  3. 张潮. 如何做好友情链接交换[J]. 计算机与网络,2016,42(23):42-43. ↩︎
  4. eallion’s Blog. 友情链接. https://www.eallion.com/links/ ↩︎
  5. 笔者按搜索结果的顺序查看了15个友链页面,仅有的提及权重的网站也只是借权重排除恶意友链https://geektutu.com/post/blog-experience-5.html ↩︎
  6. 数据来自其应用程序接口 https://bf.zzxworld.com/api/sites.数于2025年11月8日收集 ↩︎
  7. 数据来自其收录页面 https://blogwe.com/allblogs.html. 数据于2025年11月8日收集 ↩︎
  8. 数据来自首页 https://alexsci.com/rss-blogroll-network/显示其收了7952个订阅地址. 假设一个订阅地址对应一个博客可粗略估计收录了7900个博客.数据于2025年11月8日收集 ↩︎
  9. 数据来自其收录页面https://www.blogsclub.org/members.html. 数据于2025年11月8日收集 ↩︎
  10. 数据来自首页https://www.foreverblog.cn/底部的履约数据.数据于2025年11月8日收集 ↩︎
  11. 数据来自首页https://www.zhblogs.net/底部的收录博客数量.数据于2025年11月8日收集 ↩︎
分类
研究

使用 Acrylic 复现百团大战 WiFi 钓鱼攻击:关于 Klutton 学长记录的补充

起因

参见:Klutton《使用 Hostapd + dnsmasq + flask web 服务器的 WiFi 钓鱼攻击研究》

故事还要从上个月的百团大战说起,当时我看到信安协会的 Klutton 学长正对着大屏幕敲命令,我就在旁边驻足凑热闹,得知他们正在搭建钓鱼 WiFi。这个钓鱼 WiFi 的技术方案大致是用Hostapd创建热点,用 dnsmasq 实现 DNS 劫持,用 Python Flask 来 mock 学校校园网的登陆页面。

当时钓鱼 WiFi 的 SSID 被精心设置成 stu-xdwlan,登陆页面的域名也被设置为 w.xidian.edu.cn。要不是我在现场看他们配置,不然根本分辨不出来,在场地的大屏幕上我看到有挺多人上当。

因为此前略微用过 Python Flask,也粗略懂一点点(根本不懂)网页开发,所以我跟 Klutton 学长提出负责完善展示页面(其实只是显示一个 UA)。学长欣然同意了,我被拉进了代码仓库,得到了学长 mock 的校园网登陆页面。

国庆使用 Windows Acrylic 成功复现百团大战 WiFi 钓鱼攻击后,学长邀请我写一篇。(其实我这篇没什么技术含量,只作本次尝试的记录)

照抄作业

国庆之前,我就想照抄学长的作业,跟着学长的方案复现一遍。我在 Deepseek 的指导下安装了 VisualBox,安装了 Kali Linux,配置了 Hostapd 和 dnsmasq。最后在如何发射网络这个问题上犯了难。

在某宝上买了一个网卡,发现不能用,退了。又买了一个网卡,发现还是不能用,懒得退。

我的笔记本自带“移动热点”功能,但 VisualBox 配置虚拟机的时候没有网络桥接选项,让虚拟机里的 Hostapd 用上我笔记本的热点功能对我这种小菜鸡来说基本无法实现。

故,Linux 这条路是走不通了。

另寻出路

Linux 这条路给了我很大的折磨,我跟 Deepseek 说:

有没有什么在windows上就可以实现的dhcp、dns和wifi热点的程序,我不太想配置虚拟环境了

它向我推荐了 mHotspot 和 Acrylic DNS Proxy。我便用这两款软件进行尝试。mHotspot 我不知道哪里出问题了,可能是程序太老了,在我的电脑上跑不起来。

我最终用 Acrylic DNS Proxy 取得了成功。虽然过程中有一点点小波折,但这里不赘述。

如果需要用 Windows 笔记本自带的热点功能的话,需要先将热点开起来,修改热点的网络适配器设置,如图:

192.168.137.1为移动热点默认的网关IP

然后在 Acrylic 的 hosts 文件中添加这样一条,将所有的域名都解析到自己的电脑,如图:

最后,运行 Python Flask 代码,就可以了。

更进一步

关于 Acrylic 的尝试并不是一次就成功的,也是失败到让我怀疑人生。就在我怀疑人生的时候,我突然想到:能不能把热点这个身份“外包”出去呢?买一个路由器,让电脑通过网线连接路由器,并在路由器设置中将 DNS 服务器指向电脑,从而一劳永逸地解决网络发射问题,还可以增强网络的发射功率,使其更好与校园网的正版 WiFi 竞争。某宝物流显示路由器明天到货,到货了会用路由器再做一次尝试。

在上面的步骤中,我让所有的域名都解析到我的电脑上,我是否可以构造一个透明代理实现流量的中间人攻击?我向 Deepseek 分享了这个想法,它向我推荐了 mitmproxy。貌似代理加密流量需要安装并信任根证书。这个东西有待日后尝试。

是否可以对仍然支持 SSL 3.0 协议的网站进行 https 降级攻击?是否可以对不支持 HSTS 的网站进行中间人攻击?有待日后尝试。

文毕。

分类
生活

回家?

初中高中虽然都住宿,但每周还能回家一天。高三虽然可以留在学校旁边的出租房里,但最终还是没有留下过。上了大学,学校在西北边,家在东南边。回家?要跨过江西、湖北、河南。回家,不再是一件想做就能做的事了。之前高一的时候,没有体味过这般滋味,写作一篇,现在看来还是轻浮了。

“汝意谓鲤城何如日远?”答曰:“日远。不闻人从日边来,居然可知。”明日,集群臣宴会,告以此意,便重问之。乃答曰:“日近。”元帝失色,曰:“尔何故异昨日之言邪?”答曰:“举目见日,不见鲤城。”

那天晚上,冷,从图书馆走回公寓的时候,偶然想起高中语文老师讲文言文的时候介绍过这个故事。“举目见日,不见长安?”西安给我的感觉新奇,在秋分之前就能体会到闽地冬天才有的寒冷。在几个寒冷的夜晚,我貌似几次想到了这句话。偶然想要把它改成鲤城,来疏解自己的思乡。不知为何,读起来有种悲伤的味道。语文课到了大学便莫名其妙地消失了,我无从寻找这丝悲伤的来处。

我将这段文字发到朋友圈,貌似给我的父亲和母亲造成了很大的震动。

昨天晚上无意打开学信网,发现学校终于在学信网上注册了我的学籍。打开12306,看看车票。原来一学年只能买4趟学生票,坐火车回去要两三天,如果动车的话来回就要两千。我盘算着我国庆中秋的假期,基本都要在火车上度过了,见我爸我妈我弟没几个小时便要再回来了。

我将理由跟我的母亲示明,我听不清楚她在说什么,我开始犹豫到底要不要回去。

前段时间申请设立业余无线电台时,我曾经寄了一封挂号信到鲤城。在邮政的小程序上,我看着那封信从西安飞到厦门,再从厦门坐车到鲤城。一封信也能有这么美好的旅程。我想着我从学校被收走,到集散点,再飞厦门,再坐车……

今夜,当我在刷手机的时候,支付宝来了一个消息,我的母亲给我转了一笔钱。我的父亲前几天还在我们家的小群里发他在做手工活。我…我不能…

我想,给家里买盒月饼,上面写我的名字,电话填我母亲的。等会快递员打我母亲的电话,称呼我的名字……

分类
网站

Python真香:尝试开发数据爬取与后端接口

高考结束,回到开往项目组。大学录取了计算机方向的专业,之前做项目的经历告诉我开发这件事情不能靠别人,自己不太好意思再给别人提需求了,也对同龄人已经熟练使用 Node.js 感到焦虑,所以这几个月自己尝试打开电脑为项目写点东西。

此前用 PHP 和原生 JavaScript 写过一点点东西,但都不算作一个项目——要么就是从网上搜随机数来实现随机动漫图片,要么就是搜索关于数组的处理方法用函数实现了一下并集、补集之类的东西。

这并不算是一个技术笔记,只是我的碎碎念。

编写 Python 数据爬取脚本

开往此前有一个“成员文章同步”的 ToDo,但是一直没有人实现。所以,我来折腾了(实则为捣乱)。

成员文章同步计划用 FreshRSS 实现,现在只需要用一个脚本爬取开往成员的订阅地址,生成一个 OPML 文件就好了。

开往成员的订阅地址从哪里整呢?组里原先有一些想法:弄个平台让站长自行提交;内置订阅地址的一些常见 uri 去测测哪个可以访问……最后,我想到了自己此前维护过的项目——中文博客列表导航。自己之前收集的那些订阅地址不就派上用场了?我可真是一个天才(猪)。

第一次,我的想法是这样的:

用 OneNote 画的超级丑的图

但是,当我真的去按照这个思路写代码的时候,电脑报错了。检查之后发现是中文博客列表导航有频率限制。我尝试 sleep 了一下,发现效果不好。这样会给中文博客列表导航发一千多次请求,对项目不友好。

所以,我对思路进行了改进(其实并不是):首先请求中文博客列表导航和开往的全量数据(相当于复刻了两个项目的数据库),再在中文博客列表导航的数据里查开往的成员网站,找到订阅地址信息。代码碎片如下:

travellingsData = requests.get('https://api.travellings.cn/all', headers = headers)
travellingsData = list(json.loads(travellingsData.text)["data"])

regex = r"^(?:https?:\/\/)?(?:[^@\/\n]+@)?(?:www\.)?([^\/:\?\n]+)"

zhblogsRequestUrl = 'https://www.zhblogs.net/api/blog/list?page=1&pageSize=7000'
zhblogsData = requests.get(zhblogsRequestUrl)
zhblogsData = json.loads(zhblogsData.text)["data"]["data"]

def searchZhblogs(targetDomain, zhblogsAllData):
    for j in zhblogsAllData:
        if targetDomain in j["url"]:
            return j
    return 0

这里有一个细节,中文博客列表导航并没有获取全量数据的接口,怎么办呢?我发现项目的博客列表请求接口时会带一个 Size 参数。我试着把这个 Size 调成一个很大很大的值,这样数据就可以覆盖项目收录的全部博客。我试了一下,果不其然,成功了。我真是一个小机灵鬼

接下来是如何生成 OPML。我找组里的 Kegongteng 要了一份文件,看了一下文件的结构,感觉还挺简单的,就是这个 xml 不知道要用什么东西搞定。字符串拼接?我之前想过这个东西,但是肉眼可见的麻烦。懒惰的我上网搜了一下 Python 生成 xml 文件的方法,发现了一个东西,让我眼前一亮。

import xml.etree.ElementTree as ET

# 创建根元素
root = ET.Element("RootElement")

# 创建子元素
child1 = ET.SubElement(root, "ChildElement")

# 添加属性
child1.attrib["attribute"] = "value"

# 创建子元素的文本内容
child1.text = "Hello, World!"

# 创建XML文件
tree = ET.ElementTree(root)
tree.write("example.xml")

当时看到这个东西就喜欢上了。这才是适合我这种小菜鸡使用的库。Python 真香!我不知道该怎么形容我喜欢它的原因,可能是“高度的语义化”?不懂。

有了这个库,我就可以不用尝试痛苦的字符串拼接了,直接写成以下代码:

root = Element('opml')
root.attrib["xmlns:frss"] = "https://freshrss.org/opml"
root.attrib["version"] = "2.0"

# head元素
head = SubElement(root, 'head')

# head里套title元素
title = SubElement(head, 'title')
title.text = "开往成员文章订阅"

# body元素
body = SubElement(root, 'body')

outline1 = SubElement(body, 'outline')
outline1.attrib["text"] = "技术博客"

for j in travellingsTechBlogData:
    searchResult = searchZhblogs(re.match(regex, j["url"]).group(1), zhblogsData)
    if searchResult != 0 and searchResult["feed"] != None:
        outline = SubElement(outline1, 'outline')
        outline.attrib["text"] = j["name"]
        outline.attrib["type"] = "rss"
        outline.attrib["xmlUrl"] = searchResult["feed"][0]["url"]
        outline.attrib["htmlUrl"] = j["url"]
        outline.attrib["description"] = searchResult["sign"]

tree = ElementTree(root)

# write out xml data
tree.write('result-tech.xml', encoding = 'utf-8', xml_declaration = True)

最后,拖了一年多的 ToDo 就被我这么折腾完了。

整完成员文章订阅的东西之后,我还弄了一个标签同步的东西,这里不再赘述。

用 Python 写巡查工具后端

一句玩笑话

开往的巡查工具又是一个拖了很久的东西,之前大蛋糕写的前端代码还在。我来折腾(捣乱)一下。

最开始是跟组里的同学确定一下需求,是这样的:

用飞书画的模式图,比较漂亮

然后,因为我喜欢折腾,所以这个东西前端和后端分成两个人完成——前端由组里的 Lee 同学负责,后端就由我来糟蹋了。

首先是看看用 Python 的什么开发。Django?太重了,还会把前端的活给刨了。FastAPI?文档看不懂(太真实了)。为什么不用 Node.js 开发呢?因为我看不懂教程。所以,我用 Python Flask 来做后端,因为它够轻,够简单,够友好。

接着,我对要用到的接口进行了整理,大致如下:

这是我第一次做项目。虽然最后没有严格按照这样开发,但我仍然认为这样做是必要的,因为这样做我不再是无头苍蝇了。

然后,我用 PHPStudy 配了一下 MySQL 数据库环境,开始“查文档-写代码-调试”的循环。这个过程超级花时间,我还容易忘记时间。几次下午坐在电脑面前一坐就坐到夜晚。几次我爸我妈教我下楼吃饭,我都还在忙着改 SQL 查询语句,最后我爸把饭端到房间让我边写边吃。

开发过程琐碎。这里列出一些问题,以及我的解决方法(或者是组里的 Lee 和 Xuanzhi 同学告诉我的)

问题解决方法
路由挤在一个文件里面不利于维护使用 Flask 的 Blueprint
要防止 SQL 注入攻击使用参数化查询
CORS 跨域使用 flask_cors 拓展库
配置参数硬写在代码里不好使用 .env 以及 Python 相应的库

Xuanzhi 还提了一些关于接口设计的问题,比如不宜用 ifSuccess 作布尔值的名称,而应为 Success 或 isSuccess。我发现我把 is 记成 if 了。

各位大佬可以在 GitHub 上看到本小菜鸡的代码。如果有改进建议,还请批评指出。

分类
生活

社交媒体向我贩售焦虑和脆弱

我在社交媒体上把好友看得太重,发消息过去没回复就想我们的关系是不是淡了。我在社交媒体上把好友看得太重,最近常常为了网上的一点争执面红耳赤。
有约不来过夜半,闲敲棋子落灯花。我大概是没有赵师秀那样的闲趣。在轻敲桌子震落灯花时,我在担心我在别人眼里是不是根本不重要。好像我的朋友比我有更多的朋友,我对我的朋友来说并不重要。
我想过成为谁生命中重要的人吗?也许是我的父母,他们有时为我感到骄傲,有时替我感到担心,我是他们生命中重要的人。也许是我的同学和老师?哦哦,我称不上在他们生命里重要的人。我在网上的那些朋友呢?说实话,我感觉我在网上就像一次性塑料瓶,别人喝了尝过滋味了,便把我丢弃了。
社交媒体放大了我对自己的能力和人际关系的焦虑。跟我同年龄的人在参加腾讯的训练营。我焦虑,在他眼里我什么都不是,我是个彻头彻尾的废物。之前我在网上的一个朋友在他频道里发「有史以来第一次觉得自己真的是个彻头彻尾的废物」,我原想安慰他,但其实我也困在其中。