0%

Oracle MLE 爬蟲

 

Oracle 也可以搞爬蟲!? 老實說這是自虐!! 為了玩看看 Oracle MLE (Multilingual Engine) 的功能才寫這篇 , 完全嘗試用 js + pl/sql 去處理
雖然解得不太好 , 但整個解出來還是挺有趣的 , 畢竟現在沒啥範例可看
首先先到 這個網站 看看匯率

正常 js 寫法

先用正常的 js 寫看看原理 , 基本上就是用到 css 的 selector , 外加 js 把 key & value 的 array 縫合成物件就收工了

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
//取自此網站
//https://www.pnb.com.ph/index.php/foreign-exchange-rates?tpl=revamp

//撈 table
var table = document.querySelector('.table-responsive table:nth-child(2)');

//保存結果
var result = [];

//轉換 HtmlCollection 讓 foreach 可以用
var rows = Array.from(table.rows)

//跳過前兩個沒用的 row
var sikp2rows = rows.slice(2,rows.length)

//迴圈
sikp2rows.map(row => {

//轉換 HtmlCollection 讓 foeach 可以用
var cells = Array.from(row.cells);

//定義物件屬性
var keys = ['Currency' , 'Buy' , 'Sell']

//保存 cell 數值
var values = []

//迴圈取得 cell 數值
cells.map(cell => values.push(cell.innerText))

//建立匯率物件
//https://stackoverflow.com/questions/47517488/zip-arrays-as-keys-and-values-of-an-object
var rate = Object.assign(...keys.map((k, i) => ({ [k]: values[i] })));

//保存結果
result.push(rate);
});

//印出結果
console.log(result)

Oracle MLE 寫法

接著玩看看噁爛的 MLE 寫法 , 目前好像只有 require('mle-js-oracledb') require('mle-js-bindings') require("mle-js-plsqltypes") 這三個 lib 可以用 , 也不支援 npm
這三個模組的用法可以看 這邊
好在 Oracle 本身有 http request 功能可以用 , 礙於設定複雜 , 建議先看我之前寫的這篇

首先建立資料表

1
create table test_rate(Currency varchar2(100) , Buy varchar2(100), Sell varchar2(100));

接著呼叫網頁看看能否正常取得內容 , 這裡要注意 , 如果你的檔案內容超過 4000 字請一定要用 stored procedure 的方式去存 , 預設會限制 4000 字的上限
所以你如果這樣呼叫的話最多只有 4000 字 , 另外務必要設定 wallet憑證 , 不然會噴 http 錯誤

1
2
3
4
5
6
--設定錢包
EXEC UTL_HTTP.set_wallet('file:/home/oracle/wallet', 'WalletPasswd123');

--呼叫 request
SELECT UTL_HTTP.REQUEST('https://www.pnb.com.ph/index.php/foreign-exchange-rates?tpl=revamp')
FROM DUAL;

正常呼叫 http request 以後就可以新增看看資料 , 老樣子要注意網址 encode 之類的問題

1
2
3
4
5
EXEC UTL_HTTP.set_wallet('file:/home/oracle/wallet', 'WalletPasswd123');
EXEC WWW_GET('https://www.pnb.com.ph/index.php/foreign-exchange-rates?tpl=revamp');

SELECT *
FROM WWW_DATA;

最後就是最噁心的地方來了 , 在 pl/sql 裡面跑 js
本來以為可以把 table 用 regex 找出來然後轉為 oracle 的 xmltype 搭配 xml query 應該就可以查了 , 不過礙於他的結構有問題 , 所以 parser 會噴 error
所以只好整個自幹 regex 自己 parser , 要測的話用老朋友 regex101 , 我這裡忘得差不多啦 , 寫得兩光兩光的
最後一個重點注意不要寫 & 在 js 裡面 , oracle 會認為是要傳遞參數進去 , 然後會一直彈出視窗
順帶一提一開始我用 regexp_substr 去撈這個 table 的正則不曉得為啥總是跑不出來 , 丟其他正則也很慢 , 也許這就是 Oracle MLE 的價值所在吧!

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
SET SERVEROUTPUT ON;
DECLARE
ctx DBMS_MLE.context_handle_t := DBMS_MLE.create_context();
--html_content varchar2(4000);
html_content clob;
BEGIN
DBMS_MLE.eval(ctx, 'JAVASCRIPT', q'~
//example
//https://github.com/oracle-samples/mle-modules
const oracledb = require('mle-js-oracledb');
const bindings = require('mle-js-bindings');
const plsqltypes = require("mle-js-plsqltypes");


const conn = oracledb.defaultConnection();

//https://www.pnb.com.ph/index.php/foreign-exchange-rates?tpl=revamp
const query = `select dat , dat from www_data where num = 3`;

const options = { fetchInfo: { N: { type: oracledb.ORACLE_CLOB } } };
const queryResult = conn.execute(query, [], options);
const OracleClob = plsqltypes.OracleClob;
const result = queryResult.rows[0][0];

//取得 db 的 clob 長度
let length = result.length();


//從 clob 讀取 html
let html = result.read(length,1);
console.log('clob length:' + length);

//test
//https://regex101.com/

//以 regex 撈 table
let matches = html.matchAll(/<table+[^>]+>[\S\s]*?<\/table>/g);
let resultMatches = [...matches];

//取得我們需要的 table 內容
let htmlContent = resultMatches[2];

//console.log(resultMatches.length);
//console.log(resultMatches);

//以展開運算子 toString 後 , 取得 match 所有的 tr 元素 , 並且跳過前兩列撈出 tr
let rows = [...htmlContent.toString().matchAll(/<tr+.*>[\S\s]*?<\/tr>/gm)].splice(2,22);

//存放最後匯率結果
let rates = [];

//迴圈跑 tr
for(let row of rows){
console.log(row);
let m;

//保存匯率數值
var values = [];

//建立匯率物件屬性
var keys = ['Currency' , 'Buy' , 'Sell'];

//過濾 td regex
const cellsRegex = /<td+>([\S\s]*?)<\/td>|<td[\S\s]*?>([\S\s]*?)<\/td>/gm;

//迴圈跑 td
while ((m = cellsRegex.exec(row)) !== null) {
if (m.index === cellsRegex.lastIndex) {
regex.lastIndex++;
}

//迴圈取得 td 符合的數值
m.forEach((match, groupIndex) => {
if((groupIndex === 1 || groupIndex == 2) && (match !== undefined)){
values.push(match);
}
});
}

//將 value and key 組合成 json 物件
var rate = Object.assign(...keys.map((k, i) => ({ [k]: values[i] })));
//console.log(rate);
rates.push(rate);
}

//印出最後匯率的 array
console.log(rates);

//迴圈 insert 結果到 test_rate 資料表內
for(rate of rates){
console.log(JSON.stringify(rate));
//https://oracle-samples.github.io/mle-modules/docs/mle-js-oracledb/21c/
var insertResult = conn.execute(`insert into test_rate values ('${rate.Currency}' , '${rate.Buy}' , '${rate.Sell}')`);
var rowsInserted = insertResult.rowsAffected;
console.log('rowsInserted:' + rowsInserted);
}
~');
dbms_output.put_line('done');
DBMS_MLE.drop_context(ctx);
END;
關閉