MoreRSS

site iconiMaeGoo | 虹墨修改

95后前端开发者,居住郑州,擅长前端技术,分享个人学习记录。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

iMaeGoo | 虹墨的 RSS 预览

用多模态模型,写新一代爬虫

2025-03-19 08:00:00

字节有一个很实用但不怎么火的项目,叫 Midscene.js,Chrome 商店上的安装数仅有 1 万,它是一个由多模态模型驱动的前端自动化测试插件。自动化测试我平常很少用到,但我发现它特别适合用来写爬虫……

Midscene.js 一共就三大 API:Action、Query、Assert

Action 交互

描述步骤并执行交互。例如,在 GitHub 上交互:查找 GitHub 上的 Twikoo 项目,点进详情页,点个 Star——

Query 提取

从 UI 中“理解”并提取数据,返回值是 JSON 格式,想要什么数据结构,它都可以给你。例如,在面试题宝典网站上提取:string[],所有面试题目——

Assert 断言

判断是否符合指定条件。例如,在智能家庭页面断言:电脑是关着的——

大模型支持情况

项目最初仅支持 GPT-4o 模型,跑一行用例的成本在 ¥0.1 左右,还挺贵的,后来支持了 Qwen-2.5-VL 和 UI-TARS,成本就大幅降低了。以下就以千问模型为例,带领大家上手这个神奇的插件。

安装

可以直接从 Chrome 商店安装:
https://chromewebstore.google.com/detail/midscene/gbldofcpkknbggpkmbdaefngejllnief

配置

从浏览器右上角的插件菜单中打开 Midscene.js 的侧边栏,会提示 No config,点击按钮会弹出 Env Config 的设置框,在里面配置以下变量

1
2
3
4
OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
MIDSCENE_MODEL_NAME="qwen-vl-max-latest"
MIDSCENE_USE_QWEN_VL=1

其中的 OPENAI_API_KEY 需要你自己申请,申请的地址是:
https://bailian.console.aliyun.com/?apiKey=1#/api-key

以上链接不包含推广,如果你是首次开通阿里云百炼,新用户是有免费额度的,请注意额度的有效期,避免浪费~

测试

接下来用自然语言随便写一条指令,点击 Run 按钮,见证 AI 开始接管你的浏览器……

代码集成

接下来我们尝试编写爬虫,组合这三大 API,完成复杂的自动化任务。

建一个新的 Node.js 项目,安装所需的依赖——

1
pnpm install @midscene/web tsx --save-dev

编写脚本 main.ts,执行你想要进行的操作,例如,打开必应,输入 iMaeGoo 点击搜索,并输出搜索结果——

main.ts
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
import { AgentOverChromeBridge } from "@midscene/web/bridge-mode";

function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}

async function main() {
process.env.OPENAI_BASE_URL =
"https://dashscope.aliyuncs.com/compatible-mode/v1";
process.env.OPENAI_API_KEY = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
process.env.MIDSCENE_MODEL_NAME = "qwen-vl-max-latest";
process.env.MIDSCENE_USE_QWEN_VL = 1;
const agent = new AgentOverChromeBridge();
// 这个方法将连接到你的桌面 Chrome 的新标签页
// 记得启动你的 Chrome 插件,并点击 'allow connection' 按钮。否则你会得到一个 timeout 错误
await agent.connectNewTabWithUrl("https://www.bing.com");
// 这些方法与普通 Midscene agent 相同
await agent.ai("输入 iMaeGoo 点击搜索");
const result = await agent.aiQuery(
"{title: string, url: string}[], 搜索结果"
);
console.log("搜索结果", result);
await sleep(3000);
await agent.destroy();
}

main();

启动你的 Chrome 插件,点击 Bridge Mode,再点击 ‘Allow connection’ 按钮——

随后运行脚本——

1
pnpx tsx main.ts

可以看到脚本成功打开必应搜索 iMaeGoo 并打印出了搜索结果——

仅需两个文件,实现在 VS Code 状态栏监控黄金价格

2025-02-21 08:00:00

最近金价波动剧烈,要是能一边写代码,一边实时监控金价变动,就不会错过高低点了!

C:\Users\你的用户名\.vscode\extensions 新建文件夹 gold-monitor,在文件夹中创建两个文件 package.jsonextension.js

package.json
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
{
"name": "jd-gold-price-monitor",
"displayName": "JD Gold Price Monitor",
"description": "Monitor gold price from JD",
"version": "0.0.3",
"author": "iMaeGoo",
"publisher": "iMaeGoo",
"engines": {
"vscode": "^1.85.0"
},
"categories": [
"Other"
],
"activationEvents": [
"onStartupFinished"
],
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "gold-price-monitor.start",
"title": "Start Gold Price Monitor"
}
]
}
}
extension.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
const vscode = require("vscode");

let statusBarItem;
let intervalId;

function activate(context) {
// 创建状态栏项
statusBarItem = vscode.window.createStatusBarItem(
// 在状态栏左半边显示
vscode.StatusBarAlignment.Left,
// 数字越大,越靠左
-999
);
context.subscriptions.push(statusBarItem);

// 注册命令
let disposable = vscode.commands.registerCommand(
"gold-price-monitor.start",
() => {
startMonitoring();
}
);

context.subscriptions.push(disposable);

// 激活时自动开始监控
startMonitoring();
}

async function updateGoldPrice() {
try {
const price = await getPrice();
statusBarItem.text = price;
statusBarItem.show();
} catch (error) {
console.error("获取价格失败", error);
statusBarItem.text = error.message;
statusBarItem.show();
}
}

async function getPrice() {
// 获取京东金融民生银行积存金价
const response = await fetch(
"https://api.jdjygold.com/gw/generic/hj/h5/m/latestPrice?reqData={}"
);
const data = await response.json();
const price = data.resultData.datas.price;
return price;
}

function startMonitoring() {
// 清除现有的定时器
if (intervalId) {
clearInterval(intervalId);
}

// 立即更新一次
updateGoldPrice();

// 设置定时器,每隔 6666 毫秒更新一次
intervalId = setInterval(updateGoldPrice, 6666);
}

function deactivate() {
if (intervalId) {
clearInterval(intervalId);
}
}

module.exports = {
activate,
deactivate,
};

重新启动 VS Code(Ctrl + Shift + P,输入 reload window,回车)即可看到效果。

如果你想监测其他品牌金价,可以修改 getPrice 方法,具体实现如下。

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
async function zheshang() {
// 获取京东金融浙商银行积存金价
const response = await fetch(
"https://api.jdjygold.com/gw2/generic/jrm/h5/m/stdLatestPrice?productSku=1961543816"
);
const data = await response.json();
const price = data.resultData.datas.price;
return price;
}

async function zhoushengsheng() {
// 获取周生生首饰金价
try {
const res = await fetch("https://cn.chowsangsang.com/gold-info");
const data = await res.text();
const gold_data_match = data.match(/:gold_data='(.*)'/);
const gold_data = gold_data_match
? JSON.parse(decode(gold_data_match[1]))
: [];
const price = gold_data.find(
(i: any) => i.description === "黄金金价"
).price;
return price;
} catch (e) {
console.error("zhoushengsheng", e);
}
}

async function laofengxiang() {
// 获取老凤祥首饰金价
const res = await fetch("http://lfx1848.com");
const data = await res.text();
const price_match = data.match(/<span id="labContent">(.*)<\/span>/);
const price = price_match ? price_match[1] : "";
return price;
}

async function zhouliufu() {
// 获取周六福首饰金价
const res = await fetch("https://price.zlf.cn/index_35.aspx");
const data = await res.text();
const price_match = data.match(/<span class="fr">(.*)<\/span>/);
const price = price_match ? price_match[1] : "";
return price;
}

async function liufuzhubao() {
// 获取六福珠宝首饰金价
const res = await fetch("https://www.lukfookeshop.com.cn");
const data = await res.text();
const price_match = data.match(/>:(.*?)元\/克/);
const price = price_match ? price_match[1] : "";
return price;
}

async function zhoudafu() {
// 获取周大福首饰金价
const res = await fetch(
`https://api2.ctfmall.com/gateway//ctfmall-common2-server/common/ctfTodayGoldPrice?timestamp=${Date.now()}`
);
const data: any = await res.json();
const price = data.data.todayPriceHK;
return price;
}

async function laomiao() {
// 获取老庙首饰金价
const res = await fetch(
"https://vip.laomiao.com.cn/index.php/m/home-gold_price.html"
);
const data: any = await res.json();
const price = data.data.price_list.find(
(i: any) => i.name === "足金饰品"
).price;
return parseInt(price);
}

Android 14 APP 全屏代码实现

2024-11-21 21:08:00

新的 Android API 改变了应用全屏的方式,网上搜到的全屏代码都不管用了,甚至 谷歌自己的教程 都没有更新,看了 API 文档才研究明白最新代码怎么写。本篇讲述两种新的全屏方式,一种保留状态栏文字的全屏,一种隐藏状态栏的全屏。

保留状态栏,适配全面屏

默认情况下,应用界面不会延伸到系统状态栏、导航栏、导航条等区域,这会很丑,要想适配全面屏显示,在 Android 14 中,可以用一行代码简单实现。

1
2
3
// import androidx.activity.enableEdgeToEdg

enableEdgeToEdge()

隐藏状态栏,完全全屏

适配全面屏并不会隐藏系统状态栏、导航栏、导航条,如果想实现完全全屏,老办法一般是调用 setSystemUiVisibility(),然而这个方法在 14 中已经弃用了,获取 InsetsControllerViewCompat.getWindowInsetsController() 方法也被弃用了,最新的写法是:

1
2
3
4
5
6
7
8
9
// import androidx.core.view.WindowCompat
// import androidx.core.view.WindowInsetsCompat
// import androidx.core.view.WindowInsetsControllerCompat

val windowController = WindowCompat.getInsetsController(window, window.decorView)
// 应用全屏时,用户仍然可以从屏幕顶部下拉唤出状态栏,此行代码实现当用户唤出状态栏后,自动隐藏状态栏
windowController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
// 隐藏包括状态栏、导航栏、caption bar 在内的所有系统栏
windowController.hide(WindowInsetsCompat.Type.systemBars())

屠龙者终成恶龙

2024-11-14 18:00:00

赚了眼球丢了节操

前端人的 Python、Conda 环境搭建

2024-09-19 19:00:00

前言

许多 AI 相关的项目、游戏辅助工具都使用 Python 语言开发,迫使我开始接触 Python 的环境搭建。作为一个前端人,我已经对 Node.js、NVM、NPM 等工具再熟悉不过了,但搭建 Python 的过程中还是碰到了不少困难,在此记录一下,让和我一样的前端人能更快地理解 Python 的生态体系。

Python 生态

  • Python: Python 解释器和运行时环境,对应前端体系中的 Node.js,官网 www.python.org
  • Pip: Python 的包管理工具,对应前端体系中的 NPM,官网 pypi.org
  • Conda: 用于管理 Python 环境,对应前端体系中的 NVM,但又略有区别

NPM 在安装项目依赖时,会安装在项目的 node_modules 目录下,也就是局部安装,各个项目之间互相不影响,而 Pip 则是默认装在全局,这和前端的思维不一致。装在全局会造成多个项目的管理不便,各个项目之间依赖冲突、相同依赖的不同版本无法同时存在。

为了解决这个问题,Conda 就出现了,它可以创建完全隔离的多个不同的 Python 环境,每个环境可以指定不同的 Python 版本,每个环境所安装的依赖也相互隔离。

环境安装

为了搭建 Python 环境,新手往往会先想到安装 Python,但实际上 Conda 已经内置了 Python,我们 只需要安装一个 Conda,就完成环境安装了。

Conda 有三个发行的安装包,分别是 Anaconda、Miniconda、Miniforge,其中 Anaconda 最出名,它已经内置了数千个常用的科学计算依赖包,属于是开箱即用,但缺点是安装包体积达到了惊人的 912 MB。

Miniconda 就比较小巧了,它没有内置常用依赖,体积也降到了 85 MB,对于我这种需求不高的用户,显然更合适。

  1. 下载 Miniconda: https://docs.anaconda.com/miniconda/#latest-miniconda-installer-links
  2. 安装时有一步 Advanced Installation Options,建议选中 “Clear the package cache upon completion”,可以节省部分硬盘空间

修改软件源

和 NPM 一样,默认的国外软件源在国内是无法流畅使用的。我们需要在用户根目录(C:\Users\你的用户名)创建一个 .condarc 文件,输入以下内容来切换成国内源

1
2
3
4
5
6
7
8
channels:
- defaults
show_channel_urls: true
default_channels:
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
- https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/pytorch

修改环境位置

如果你不想让以后安装的依赖占用 C 盘的空间,还需要提前修改环境位置,在上一步的 .condarc 文件中增加两行

1
2
envs_dirs:
- D:\dev\condaenvs

环境创建

在开始中搜索 “anaconda”,找到 “Anaconda Prompt (miniconda3)” 并打开,然后执行

1
conda create -n 环境名称 python=3.12 -y

替换命令中的环境名称为你定义的环境名称,替换命令中的 3.12 为你需要的 Python 版本

这样就完成环境的创建了,执行 conda env list 可以查看环境列表

环境切换

仍然是在 Anaconda Prompt 中执行

1
conda activate 环境名称

这样就切换到了对应的环境中

依赖安装

仍然是在 Anaconda Prompt 中执行

1
2
cd /d 项目目录
pip install -r requirements.txt

注意 -r 后跟项目依赖声明文件,有可能叫 requirements.txt,也有可能叫 requirements-prod.txt,之类的

安装好依赖后,就可以启动项目啦!具体如何启动可以参考项目的 README 文件。

在 bat 中切换 Conda 环境

有时候我们需要编写 bat 脚本来执行一些自动化任务(例如定时执行),这时我们会发现,由于脚本执行时没有切换到指定的 Conda 环境中,会报 Python 找不到的错误。怎样在 bat 脚本中切换 Conda 环境呢?

1
2
3
4
5
@echo off
cd /d 项目目录
call D:\dev\anaconda3\Scripts\activate.bat D:\dev\anaconda3
call conda activate 环境名称
python 项目主入口.py

替换命令中的 D:\dev\anaconda3 为你的 Conda 安装路径,替换最后一行命令为项目的启动命令

配置 PyCharm

如果你和我一样在用 PyCharm 开发工程,还需要在其中配置工程所使用的 Conda 环境

  1. 在 PyCharm 中打开工程
  2. 打开 文件-设置-项目-Python 解释器-添加解释器-添加本地解释器

  1. 选择 Conda 环境,选择 conda.exe 所在位置,然后选择刚才创建好的环境,然后确定

  1. 回到设置窗口选择刚刚添加好的解释器,大功告成

给 Parsec 设置代理,解决国内无法打开、无法登录、无法刷新的问题

2024-09-18 15:19:00

Parsec 一直是我用过的体验最好、最流畅的远程串流软件,它能实现几乎无感的远程体验,配置上也比 Sunshine + Moonlight + 内网穿透的方案要简单得多。Parsec 只需要一个软件就能实现点对点远程。同时它也是 macOS 上我用过的最好用的远程被控端。

但最近这个软件无法正常打开了,原因是国内无法访问 Parsec 的服务器。对此,我们可以寻找一台可以正常连接 Parsec 的代理服务器,这里以 127.0.0.1:7890 为例,让 Parsec 恢复正常使用。

为 Parsec 配置代理以后,Parsec 只会在启动、登录、刷新电脑列表、建立连接时使用代理服务器,实际串流仍然不经过代理服务器,而是使用点对点连接,所以不用担心代理服务器对串流性能造成影响!

  1. 打开 Parsec 的配置文件

如果是 Windows,配置文件可能在以下两个位置其中之一,按 Win + R 输入位置以后确定,找得到哪个就改哪个:

  1. %APPDATA%\Parse\config.json
  2. C:\ProgramData\Parsec\config.json

如果是 macOS,请打开 ~/.parsec/config.json(可以用 open -e ~/.parsec/config.json 命令直接打开)

  1. 在第一个花括号 { 后面增加这 9 行代理配置,注意加完以后整个文件必须是一个合法的 JSON,否则 Parsec 会重置配置
1
2
3
4
5
6
7
8
9
"app_proxy": {
"value": true
},
"app_proxy_address": {
"value": "127.0.0.1"
},
"app_proxy_port": {
"value": 7890
},

  1. 保存以后,打开任务管理器,结束所有 parsecd.exe 进程,然后再打开 Parsec,就可以正常使用了。