0%

Chrome 开发必装插件

清除浏览器缓存:chrome://settings/clearBrowserData

强制https设置:chrome://net-internals/#hsts

管理扩展程序:chrome://extensions/

清除 DNS 缓存:chrome://net-internals/#dns

  • chrome://settings/
  • 系统
  • 使 使用图形加速功能(如果可用) 保持关闭
阅读全文 »

FTP 即 File Transfer Protocol 文件传输协议。

客户端与服务端建立会话,双方启动控制进程,用到TCP的21端口,利用此端口控制文件是上传还是下载,删除或是复制等操作命令,真正的数据传输走的是另外的端口。

阅读全文 »

Windows-Dos命令

切换磁盘

1
2
C:\Users\Administrator>d:
D:>

查看当前目录下所有的文件和目录

1
D:> dir

创建文件夹

1
D:> md java

切换文件夹

1
2
3
4
5
6
D:> cd java
D:\java> cd..
D:> cd..
D:> cd java
D:\java> cd/
D:>

写创建文件

1
echo name:Tom,age:12>1.doc

删除文件

1
2
3
del 1.doc
# 删除所有以 .txt 为结尾的文件
del *.txt

删除文件夹 remove directory

1
2
3
4
5
6
# 删除空目录
rd java-empty

# 删除非空目录
del java
rd java

代码自动补全

参考

字体

可到这里下载powerline字体, 我使用的是UbuntuMono, 配合vim-airline使用。

Ack - 用于检索关键字

1
brew install ack
  1. Neovim

  2. Vim Plug to install extensions

  3. coc.nvim for autocompletion and imports and type definitions

    1. ctrl-space autocompletion
    2. gd goto definition
    3. F2 rename
    4. K show tooltip
    5. created coc-pref.json
    6. :CocInstall coc-snippets
      1. :CocCommand snippets.editSnippets
  4. Gruvbox theme

  5. yats for typescript syntax

  6. nerdtree for file tree

    1. auto open
    2. install fira font

    Plug ‘tsony-tsonev/nerdtree-git-plugin’ Plug ‘tiagofumo/vim-nerdtree-syntax-highlight’ Plug ‘ryanoasis/vim-devicons’

  7. nerdcommenter

    1. cmd / comment out code
    2. (mapped to ++ so cmd / can be mapped)
  8. ctrlp.vim for fuzzy file finder

    1. cmd p open
  9. cmd keybindings required iTerm2 remappings

  10. vim-gitgutter tells you what lines you modified

参考自:https://www.notion.so/Vim-Setup-0586a2ae8466442daa293ad1297bdcd0

N1 系统安装完成后,为了使外网登录更加安全,本篇将介绍如何把 ssh 密码 登录方式改为 ssh 证书 免密登录。

阅读全文 »

ddrk m3u8视频源地址解码

低端影视 网站可观看各种电影,网速及分辨率也满足需求,但唯独没有视频对应下载地址(网络高峰期不给力,经常卡顿,无法正常观影~),通过网络请求分析,应该是 m3u8 格式的资源。

m3u8 index 文件

这里以星际迷航电影源解析为例:

打开链接地址 https://ddrk.me/star-trek-discovery/?ep=1,打开 chrome 开发者工具,在 network 面板中监听 XHR 异步网络请求,点击视频可观察到:

image-20201115155154892

image-20201115155239705

第一个 video 请求应该就是 m3u8 文件的 index,但响应值中的 pin 明显是加密过的。

接着通过 Sources 面板,全局搜索下源代码中的 pin 关键字,最终找到相关后续处理的 javascript 代码,里面有 ungzip 关键字,可得出是 pin 值通过 gzip 加密的。

image-20201115155509206

而这里也可以看出结果是通过第三方库 pako,接着我们本地搭建下开发环境,模拟解析下 pin 值,示例代码如下:

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
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/pako/1.0.6/pako.min.js"></script>

</head>
<body>
<button onclick="decode()">decode</button>
<div id="ciphertext"></div>
</body>
</html>
<script type="text/javascript">
function decode(){
$.ajax({
//headers: { 'origin': "https://ddrk.me" },
type: "get",
url: "http://test.codezm.com/test/cors",
data: {},
dataType: "json"
}).done(function(data) {
console.log(data)
var encodedData = data.pin;
//var decodedData = window.atob(encodedData);
var data = pako.ungzip(encodedData, {to: "string"});
$('#ciphertext').text(data);
}).fail(function(){

})
}
</script>

服务端对应做了个可跨域请求接口,PHP 代码如下:

1
2
3
4
5
6
7
8
9
public function corsAction() {
header("Access-Control-Allow-Origin: *");

$result = Tools_help::getget('https://v.ddrk.me:9543/video?id=lKwK4yQuvITOqPfIV5uAXv4YBvZ0qNX5owCjNlFG3JixIwBLvmOcfTj442xQP28mq17kzCVdO8BhYqQP4tHv36OKHGdc5oM5liL2h86V4j3CnHXFUF6W07W5%2BDu7LfwYXbdBDNqxxKahlDQIz7lZwQ%3D%3D&type=mix');
$result = json_decode($result, true);

Tools_help::ajaxReturn($result);
exit;
}

此时访问前端页面,即可解出 index 地址。

image-20201115154854708

总结
  1. index 请求地址有时效性,过期后需要重新获取 index 地址。

  2. index 地址做了 origin 限制,需要在服务端做个跨域中转请求,才能拿到其响应数据。

  3. index 中的 ts 地址没有时间限制。

今天接到任务需要抓取下我司抖音号里所有视频的:点赞数、评论数、转发数 数据。大概是因为临近年终了。。。

抓取流程解析

  1. 开启代理工具

  2. 手机添加代理

  3. 打开抖音App,进入对应抖音账号,开始抓取数据,重复通过手势向上滑动以加载更多数据,直至显示所有视频。

  4. 抓包完毕。

工具准备篇

1
2
3
4
5
6
$ npm i -g whistle whistle.autosave mysql --registry=https://registry.npmmirror.com

# 将源地址改为淘宝源
$ npm config set registry https://registry.npmmirror.com
# 将源地址改为官方源
$ npm config set registry https://registry.npmjs.org
  • whistle 网络数据抓包。
  • whistle.autosave whistle 官方插件,默认将 response 数据记录到文件。对其改造(仅获取需要的字段数据)并存储至数据库。
  • mysql MySQL 数据库操作包,用于将数据存储到数据库。

抓包数据分析

通过 w2 run 启动 whistle 抓包工具。

在抓取数据之前需要找到对应数据 URLresponse 字段。

视频列表
  • https://api3-core-c-hl.amemv.com/aweme/v1/aweme/post/
  • https://api5-core-c-hl.amemv.com/aweme/v1/aweme/post/
视频分享地址
  • https://www.iesdouyin.com/share/video/${aweme_id}

涉及字段

resBodyJSON 格式的 body 数据。

  • resBody.aweme_list[i].statistics.digg_count 点赞数量
  • resBody.aweme_list[i].aweme_id 视频ID
  • resBody.aweme_list[i].desc 视频描述
  • resBody.aweme_list[i].statistics.comment_count 评论数量
  • resBody.aweme_list[i].statistics.share_count 转发数量
  • resBody.aweme_list[i].create_time 发布时间
  • resBody.aweme_list[i].duration 视频时长

添加匹配规则

通过 抓包数据分析 得知,host 是变动的,而剩下的 URI 是固定格式。

浏览器,打开 http://127.0.01:8899/#plugins 地址,添加过滤条件 /aweme\/v1\/aweme\/post/,这时只有匹配到过滤条件的请求才会请求到 whistle.autosave 插件。

创建数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE `douyin` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`sid` bigint unsigned NOT NULL DEFAULT '0' COMMENT '视频ID',
`name` varchar(1000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '视频描述',
`digg` int unsigned NOT NULL DEFAULT '0' COMMENT '点赞数量',
`comment` int unsigned NOT NULL DEFAULT '0' COMMENT '评论数量',
`share` int unsigned NOT NULL DEFAULT '0' COMMENT '分享数量',
`duration` int unsigned DEFAULT '0' COMMENT '视频时长',
`create_time` int unsigned DEFAULT '0' COMMENT '发布时间',
`update_time` int unsigned DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `id` (`sid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

whistle.autosave 插件改造

1
2
$ cd $(npm root -g)/whistle.autosave/lib
$ vim resStatsServer.js

编辑文件 resStatsServer.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
const fs = require('fs');
const path = require('path');
const { check: checkFilter, update: updateFilter } = require('./filter');

const MAX_LENGTH = 10;
const noop = () => {};
var mysql = require('mysql');

var connection = mysql.createConnection({
host : '127.0.0.1',
user : 'root',
password : '1234',
port: '3306',
database: 'test',
charset: 'UTF8MB4_BIN'
});

const formatDate = function (now) {
var year = now.getFullYear();
var month = now.getMonth() + 1;
var date = now.getDate();
var hour = now.getHours();
var minute = now.getMinutes();
var second = now.getSeconds();

return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
};

connection.connect();

const insertSql = 'INSERT INTO douyin(sid,name,digg,comment,share,create_time,duration,update_time) VALUES(?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE digg = ?, comment = ?, share = ?, create_time = ?, update_time = ?';

module.exports = (server, { storage }) => {
let sessions = [];
let timer;
const writeSessions = (dir) => {
try {
const text = JSON.stringify(sessions.slice(), null, ' ');
sessions = [];
//dir = path.resolve(dir, `${Date.now()}.txt`);
dir = path.resolve(dir, `浪涨小青岛.txt`);
fs.writeFile(dir, text, (err) => {
if (err) {
fs.writeFile(dir, text, noop);
}
});
} catch (e) {}
};

updateFilter(storage.getProperty('filterText'));
server.on('request', (req) => {
// filter
const active = storage.getProperty('active');
if (!active) {
return;
}
const dir = storage.getProperty('sessionsDir');
if (!dir || typeof dir !== 'string') {
sessions = [];
return;
}
if (!checkFilter(req.originalReq.url)) {
return;
}
req.getSession((s) => {
if (!s) {
return;
}

var resBody = JSON.parse(s.res.body);
//var currentTime = Date.now().toString().substr(0, 10);
var currentTime = parseInt(Date.now() / 1000);
for(var i=0;i<resBody.aweme_list.length; i++) {
console.log(
resBody.aweme_list[i].aweme_id,
resBody.aweme_list[i].create_time,
resBody.aweme_list[i].desc,
resBody.aweme_list[i].statistics.digg_count,
resBody.aweme_list[i].statistics.comment_count,
resBody.aweme_list[i].statistics.share_count,
formatDate(
new Date(parseInt(resBody.aweme_list[i].create_time + "000"))
)
);

var insertSqlData = [
resBody.aweme_list[i].aweme_id,
resBody.aweme_list[i].desc,
resBody.aweme_list[i].statistics.digg_count,
resBody.aweme_list[i].statistics.comment_count,
resBody.aweme_list[i].statistics.share_count,
resBody.aweme_list[i].create_time,
resBody.aweme_list[i].duration,
currentTime,
// update
resBody.aweme_list[i].statistics.digg_count,
resBody.aweme_list[i].statistics.comment_count,
resBody.aweme_list[i].statistics.share_count,
resBody.aweme_list[i].create_time,
currentTime,
];
connection.query(insertSql, insertSqlData, function (err, result) {
if(err){
console.log('[SQL ERROR]', err.message);
return;
}

console.log('INSERT OR UPDATE:', result.insertId, result.affectedRows);
});

});
});
};

启动抓包程序

1
$ w2 run

蝉妈妈

我同事之前也做过类似的事情,但通过的是第三方平台再处理的数据。跟同事要来了一份数据,对比下第三方平台数据与原数据的差异:

  1. 确认蝉妈妈90天数据,有无丢失视频
1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT
*
FROM
douyin
WHERE
-- 推算今天(2021-01-12) 近90天日期:(2020-10-14 00:00:00)
sid NOT IN ( SELECT aweme_id FROM dylist WHERE NAME = 'xxx' AND `publish_time` > 1602604800 )
-- 今日抓取蝉妈妈最后一个视频发布时间: (2021-01-12 05:33:55)
AND create_time <= 1610400835 AND create_time > 1602604800
ORDER BY
create_time DESC;

-- 共丢失 184 条视频,最近一条视频发布时间:2020-12-17 09:30:48


2. 计算差异

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
-- select count(*) from dylist where name = 'xxx' and `publish_time` > 1602604800;
-- 4419
-- select count(*) from douyin where create_time <= 1610400835 AND create_time > 1602604800;
-- 4603

SELECT
a.NAME,
a.`comment`,
a.digg as "点赞A",
a.`comment` as "评论",
a.SHARE as "转发",
b.digg_count as "点赞B-蝉妈妈",
b.comment_count as "评论-蝉妈妈",
b.share_count as "转发-蝉妈妈",
( CONVERT ( a.digg, SIGNED ) - CONVERT ( b.digg_count, SIGNED ) ) AS "点赞差(A-B)",
( CONVERT ( a.comment, SIGNED ) - CONVERT ( b.comment_count, SIGNED ) ) AS "评论差",
( CONVERT ( a.share, SIGNED ) - CONVERT ( b.share_count, SIGNED ) ) AS "转发差",
FROM_UNIXTIME( a.create_time, '%Y-%m-%d %H' )
FROM
douyin AS a
right JOIN dylist AS b ON a.sid = b.aweme_id
WHERE
b.NAME = 'xxx'
-- 今日抓取蝉妈妈最后一个视频发布时间: (2021-01-12 05:33:55)
AND a.create_time <= 1610400835
AND a.create_time > 1602604800
-- 推算今天(2021-01-12) 近90天日期:(2020-10-14 00:00:00)
AND `publish_time` > 1602604800;
ORDER BY a.create_time desc;
总结
  1. 第三方平台并不只是抓取 90 天之内的视频,有可能只是记录了,并显示出对应视频数据,在同事抓取时仅做了更新操作,并未对有差异的再做更新。
  2. 第三方平台视频数据缺少。
  3. 第三方平台可能对历史视频数据不再处理了,导致点赞数、评论数等数据统计不准确。

后记

部分视频可以直接通过 URL 获取,有时限(过一段时间后将无法获取数据)。

  • https://api3-core-c-hl.amemv.com/aweme/v1/aweme/post/?page_from=1&user_id=3293759164133159&publish_video_strategy_type=2

  • https://api3-core-c-hl.amemv.com/aweme/v1/aweme/post/?page_from=2&user_id=68310389333&publish_video_strategy_type=2

  • https://api3-core-c-hl.amemv.com/aweme/v1/aweme/post/?count=21&publish_video_strategy_type=2&page_from=2&user_id=566969479994331

参考