顯示具有 node 標籤的文章。 顯示所有文章
顯示具有 node 標籤的文章。 顯示所有文章

2023年8月31日 星期四

Node.js 筆記 - 使用 Puppeteer 抓取網頁上的圖片內容並靠 Tesseract 分析圖片上的文字 (OCR) @ macOS 13.5.1, node v20.5.1, tesseract v5.3.2

剛好有同好在詢問如何處理認證碼的事,就順手複習一下 node.js 與 Puppeteer 動態爬網頁,並且把認證碼圖片存起來,在靠 Tesseract 分析上述的文字。

以前略知 光學字元辨識(OCR) 的方式,但一直沒實戰,這次就順手摸一下:github.com/changyy/node-puppeteer-with-tesseract-tool

目前只單純練一下功,還不到實際使用的地步,因為 Tesseract 分析上仍有精準度問題,純做個小工具方便把玩。此程式碼只提供快速定位到 <img> (getElementById)  的方式,接著動態存成圖片,再呼叫工具分析它。

此外,已經存在本機了,後續也能靠 tesseract 指令反覆測試參數。

安裝:

% sudo port install tesseract tesseract-eng tesseract-chi-tra tesseract-chi-sim 
% sw_vers
ProductName: macOS
ProductVersion: 13.5.1
BuildVersion: 22G90
% tesseract --version
tesseract 5.3.2
leptonica-1.82.0
libgif 5.2.1 : libjpeg 8d (libjpeg-turbo 2.1.5.1) : libpng 1.6.40 : libtiff 4.5.1 : zlib 1.2.11 : libwebp 1.3.1 : libopenjp2 2.5.0
Found SSE4.1
Found libarchive 3.6.2 zlib/1.2.11 liblzma/5.4.1 bz2lib/1.0.8 liblz4/1.9.4 libzstd/1.5.4
Found libcurl/8.1.2 SecureTransport (LibreSSL/3.3.6) zlib/1.2.11 nghttp2/1.51.0

使用:

% nvm use v20
Now using node v20.5.1 (npm v9.8.0)
% npm install
% npm run main
> main
> node main.js
Usage> node /private/tmp/node-puppeteer-with-tesseract-tool/main.js "WebURL" "ImageObjectID"
% node main.js
Usage> node /private/tmp/node-puppeteer-with-tesseract-tool/main.js "WebURL" "ImageObjectID"

測試:

% node main.js 'https://xxx/login' 'verifyImgCode'
[INFO] WebURL: "https://xxx/login", The id of the DOM <img>: "verifyImgCode"

  Puppeteer old Headless deprecation warning:
    In the near future `headless: true` will default to the new Headless mode
    for Chrome instead of the old Headless implementation. For more
    information, please see https://developer.chrome.com/articles/new-headless/.
    Consider opting in early by passing `headless: "new"` to `puppeteer.launch()`
    If you encounter any bugs, please report them to https://github.com/puppeteer/puppeteer/issues/new/choose.

on.domcontentloaded
on.framenavigated: about:blank
on.load
...
page.screenshot
browser.close
tesseract.recognize result:  058B47

練精準度:

% tesseract /tmp/verify-code.png stdout -c tessedit_char_whitelist=0123456789

2021年9月10日 星期五

Node.js 筆記 - Loopback 2 與 MySQL 查詢緩慢的瓶頸排除 @ loopback-connector-mysql, connectionlimit

花了兩天幫同事追一個問題:為何有個服務重啟後,一直處於不穩甚至無法服務的狀態,但隨著時間是有機會變穩定的。

這算是一個 IoT 老服務了,當裝置上線時會跟服務保持連線(Websocket),其中有一些認證跟服務會使用到 DB server。當運行好一段時間,沒什麼大問題,反應速度也很良好。但碰到服務發布時,都會有一段期間無法正常使用。

此現象主要是 IoT 裝置們當初設計了自動連上雲台的架構,每台裝置啟動後,依序連上雲服務。但是,當服務更新時,若採到斷線機制時,將導致大量的裝置重新連線,而連線過程若有要認證就會佔用到 DB query 資源。此時若有些回應慢,將導致有些裝置認為服務不正常而自行斷線,並且又進入 retry 機制,導致災難性的自家人打自家人 DDoS 世界奇觀。

追了好一陣子,鎖定了一套使用 node.js - loopback 2 框架的服務,並且追到 DB query 瓶頸。主要透過對 db 查詢結果做 cache ,可以大幅度改善狀態,去減少 db query。並且當下 db 並未忙碌到回應緩慢。包括用其他對應機器追蹤查詢 db server 效率,以及在雲台出問題的機器上,直接用 mysql command line 查詢,即可交叉驗證應當是 node.js - loopback 的狀態問題。

原本也有意先把套件都升一升,可惜要跳到 LoopBack 4 並不是簡單的事 Orz 最後,老覺得這件事還是不太正常,就用 concurrent limit loopback mysql 關鍵字逛逛,大部分都在討論連線異常,很少提到連線數的問題,很幸運最後找到 connectionlimit 關鍵字,並且在查一下 loopback-connector-mysql 得知預設是只用 10 這個數字!

  
if (isNaN(s.connectionLimit)) {
    s.connectionLimit = 10;
  }

因此,就只需要在 loopback 框架下的 datasources.json ,多定義 connectionLimit 即可,例如來個 100 ,並且在系統上追蹤連線數:

$ netstat -np --inet | grep "YourDBServerIP:3306" | wc -l
100

這樣就搞定啦!當時想說 loopback 2 太舊太難升,是不是弄個類似 mysql command line 查詢機制來頂著用 XD 還因此配合 server 環境用 php5 寫好小工具了:

% echo '{"db_host":"DB_IP","db_user":"DB_USER","db_pass":"password","db_database":"DB_NAME","db_query":"SELECT count(*) FROM device;"}'  | jq ''     
{
  "db_host": "DB_IP",
  "db_user": "DB_USER",
  "db_pass": "password",
  "db_database": "DB_NAME",
  "db_query": "SELECT count(*) FROM device;"
}

% echo '{"db_host":"DB_IP","db_user":"DB_USER","db_pass":"password","db_database":"DB_NAME","db_query":"SELECT count(*) FROM device;"}'  | php db_helper.php /tmp/db_helper.log N M | jq ''
{
  "input": [
    [
      {
        "count(*)": "1234567"
      }
    ]
  ],
  "log": [
    0.34358501434326,
    0.11107587814331,
    0.47047901153564
  ],
  "status": true
}

還設計了 db_helper.php 能不能支援最多跑 N 隻,每隻也來個 timeout M 秒,並透過 lock 機制來保護溝通等,想想這段旅行也是滿好玩的。先把尚未妥善測試的 php5 工具丟到 github 記一下。

2017年1月13日 星期五

[NodeJS] 批次處理 Website snapshot 並存進 MySQL DB @ Ubuntu Server 14.04

延續之前 [NodeJS] 使用 WebShot 進行網頁截圖、顯示正確的中文(CJK)等編碼 @ Ubuntu 14.04 Server 的部分,稍微改幾行 code 就支援批次處理啦

前置環境:

$ sudo apt-get install nodejs npm xfonts-wqy xfonts-kaname
$ sudo ln -s /usr/bin/nodejs  /usr/bin/node
$ mkdir -p job/images && cd job
$ npm install webshot
程式主體:

$ vim build.js

var output_dir = 'images';
var concurrent_limit = 10;
var running_task = 0;
var total_task = [
{ domain: 'tw.yahoo.com', url: 'https://tw.yahoo.com' } ,
{ domain: 'facebook.com', url: 'https://facebook.com' } ,
];

function build_website_snapshot() {
while(total_task.length > 0 && running_task < concurrent_limit) {
var item = total_task.shift();
var url = item.url;
var domain = item.domain;

// https://github.com/brenden/node-webshot
webshot(url, output_dir+'/'+domain+'.png', {
screenSize: {
width: 320,
height: 480,
},
shotSize: {
width: 320,
height: 320,
},
timeout: 20000,
renderDelay: 3000,
userAgent: 'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.20 (KHTML, like Gecko) Mobile/7B298g'

}, function(err) {
if(err)
console.log(err);
running_task--;
if (running_task == 0)
console.log('done');
if (total_task.length > 0)
build_website_snapshot();
});
running_task++;
}
}


$ node build.js

如此一來,就稍微搞定批次產出了。若要把產出的東西存進 MySQL DB server,那可以再這樣做:

$ npm install mysql

$ vim import.js

var fs = require('fs');
var path = require('path');
var mysql = require('mysql');
var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'dbuser',
  password : 'dbpassword',
  database : 'dbname',
});

var scan_source_dir = 'images';
var files = [];
var sql_values = [];
fs.readdirSync(scan_source_dir).filter(function(file){
        //console.log(file);
        if (fs.statSync(path.join(scan_source_dir, file)).isFile() && file.lastIndexOf('.png') == (file.length - 4)) {
                var domain = file.substring(0, file.length - 4);
                //files[domain] = fs.readFileSync(path.join(scan_source_dir, file), {encoding: 'binary'});
                files[domain] = fs.readFileSync(path.join(scan_source_dir, file));

                sql_values.push([domain, files[domain], Math.round(new Date().getTime()/1000), Math.round(new Date().getTime()/1000)]);
        }
});
// console.log (sql_values);
/*
CREATE TABLE `snapshot_table ` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `domain` varchar(64) NOT NULL DEFAULT '',
  `image` blob,
  `createtime` int(11) DEFAULT NULL,
  `updatetime` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `domain` (`domain`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
*/

var sql = "INSERT INTO snapshot_table (domain, image, createtime, updatetime) VALUES ? ON DUPLICATE KEY UPDATE image=VALUES(image), updatetime=VALUES(updatetime) ";
connection.query(sql, [sql_values], function(err) {
        console.log(err);
});
connection.end();


如此一來,就可以自動掃目錄下符合 *.png 的檔案,並將 binary data 紀錄至 db server 中。

2015年8月29日 星期六

Node.js 筆記 - 使用 Bloom Filter 實作大量 URL Checker/Filter (SPAM)

最近想要針對 UGC 資料進行過濾,首先要面對的就是 SPAM/18+ 資料,接著,沒想到聽到 Bloom Filter 字眼!這個學生時代就碰過的東西,竟然會被提及,就那把玩一下吧!對於 Bloom Filter 的用法需要留意一下,本身設計是用時間省空間的架構,並且有誤判的機率,只要用得恰當都還好啦。

有點久沒摸 node.js 了,就挑他來試試,立馬找到有人已經實作好了 XD https://www.npmjs.com/package/bloomfilter 我只好接著使用啦 :P

接著不知不覺就蹦出一個 url-spam-checker 了 :P 有需要想把玩的可以試試:

$ npm install url-spam-checker
$ vim test.js
var
sys = require('sys'),
path = require('path'),
url_spam_checker = require('url-spam-checker')
;

url_spam_checker.createServer([
function handle_resource(callback){
var ret = [];
ret.push('yahoo.com');
ret.push('google.com.tw');
callback(null, ret);
}
], 8000, sys.log, sys.log);
$ node test.js


就會在 8000 port 起了 HTTP server ,接著可以用 http://localhost:8000/?url=http://tw.yahoo.com 的方式測試,若存在會回傳 HTTP 200 OK,不存在則是 HTTP 404 NOT FOUND。

當要驗證的 URL = http://tw.yahoo.com 也會被判定 200 OK,因為這邊是檢驗 domain level 的,不是 hostname 而已,同理在 URL = http://yahoo.com.tw 則是 404 NOT FOUND。而上述 handle_resource 可以自行填寫,例如回傳個20萬筆也是 ok 的。

這邊在使用 Bloom Filter 時,其 bit space =  Pattern 數量 x 256,這是因為我設定 hash function 有 8 組,所以 256 = 2^8  囉。

有興趣可以參考這邊:https://www.npmjs.com/package/url-spam-checker

2015年1月2日 星期五

MongoDB 開發筆記 - 替 Mongoose (Node.js MongoDB Driver) 送 patch 的經驗

大概 2014 年 11 月附近,下了班仍會抽空把玩 MongoDB ,當時就決定搭配 node.js 來把玩 :P 當時在挑選 Node.js MongoDB Driver 時,看到官方文件提到了 Mongoose ,就順勢把玩了幾下。

對於 nodejs - mongoose 跟 nodejs - mongodb 的差異點在於 mongoose 做了滿多整合方式,例如 QueryStreams 的效果,在處理大量資料時,非常便利!

除了適應 node.js 的 async 架構外,又發現 mongoose 跟原生 mongodb 設計上已經有些 out of date,在 Stack Overflow 和 mongoose issue 有不少抱怨,就試著 trace mongoose 架構,再送看看 patch,很幸運地被接受了 :P 而且只多寫了 2~3 行 code,果真好的 framework 都已經設計的很好,可能花了 15 分鐘了解架構,而 testing 花了 30 分鐘,結果 coding 才 3 分鐘 XDD

只是送 patch 的過程並沒有很順利,包含未先讀 mongoose / CONTRIBUTING.md 的描述就直接上 code,被挑了 coding style 問題,另外,還沒有跑 unit test XDDD 就碰到 CI 回報錯誤 Orz 接著才發現補上的那段 code 並不嚴謹。把 code 嚴謹化後,CI 也直接回報可以 merge :) 就這樣非常快地 2 天內就被 merge 進 master 了。

這次 patch 經驗:

  • 開發上碰到需求不被滿足,接著搜尋 Stack Overflow 及該專案的 issue tracking,發現問題一直沒被解決時,就可以著手看看要不要抽時間來貢獻
  • 接著發現解法後,一定要先看專案的 CONTRIBUTING 規定
  • 不錯的專案一定都有 unit test,記得跑一下
  • 提供簡單的 testing 程式碼跟預期結果,可以加速 commiter 閱讀及提升認同感

剩下的就只要花時間等待了 Orz 例如這次的 commiter 是 MongoDB New York RD,作息剛好日夜顛倒,因此每一次完整的互動大概都花了 2 天。

2014年6月1日 星期日

[NodeJS] 透過 Wget + HTTP Proxy Server 架構進行 Content 分析

前陣子研究抓取資料時,發現 web crawler 搭配 proxy 時,可以在 proxy 這層進行資料的處理、分析,就一直思考到底要用 python 還是 node.js 測試,最後,實在是 node.js 方便許多,就...衝啦。

在進行之前,小提一下 Proxy Server 本身就有 Internet Content Adaptation Protocol (ICAP) 機制,其實只需要架一個 Squid 在寫一些 ICAP 即可達到類似的效果,有興趣可參考:[Python] 使用 PyICAP 淺玩 ICAP 與 Squid (以 Response Modification / RESPMOD 為例) @ Ubuntu 12.04

對於沒開發過 node.js 的我,就先參考 node-http-proxy 的目錄結構建立起來一個 open source:node-content-filter-proxy

這兩天抽空把玩的心得:
  • 支援 HTTP Requests
  • 尚未處理 HTTPS Requests

抽取 <a href="...">...</a> 用法:


$ cd node-content-filter-proxy/examples && npm install
$ clear;node extract-hmtl-a-href.js
1 Jun 10:20:22 - Service Running at 3128 Port


透過 wget proxy usage:

$ http_proxy="http://localhost:3128" https_proxy="http://localhost:3128" wget -qO -  http://www.google.com/
<a href="http://www.google.com.sg/imghp?hl=en&tab=wi">Images<a/>
<a href="http://maps.google.com.sg/maps?hl=en&tab=wl">Maps<a/>
<a href="https://play.google.com/?hl=en&tab=w8">Play<a/>
...


簡單的說,想要透過既有的 crawler (wget) 去下載資料,但是,資料中我只對 hyperlink 感到興趣,所以透過 proxy server 更新成只回傳 hyperlink 就好,維持 hyperlink 結構可以讓 wget --recursive 抓資料。

如此一來,原先 crawer = wget 架構,轉形成 crawler = wget + proxy server + content analysis 模式,可以專心擺重在內容分析,不必從頭刻一隻類似 wget 的程式。

最後一提,在 extract-hmtl-a-href.js 範例中,採用的不是透過 regular expression 去分析 link ,而是接近一隻 Javascript Rendering Engine (cheerio) ,所以未來可以做的事更多了 :) 至於效率部分倒還好,畢竟 crawler 抓太快會被 ban 掉,此外,分析 content 時,也能設計複雜更高的 seed list (回吐  hyperlink 時),能避開 DOS 現象(例如限定某個 domain 、網站一天只抓2000筆等)。

2014年5月25日 星期日

[NodeJS] 使用 WebShot 進行網頁截圖、顯示正確的中文(CJK)等編碼 @ Ubuntu 14.04 Server



把玩一下 node.js ,發現有個套件不錯叫做 webshot,非常簡易地就可以把網站截取下來。然而,在 Ubuntu 14.04 server 版上運行時,發現無法正常顯示中文字,需要額外處理,就筆記一下:

安裝 Node.js:

$ sudo apt-get install nodejs npm
$ sudo ln -s /usr/bin/nodejs  /usr/bin/node


使用 webshot:

$ mkdir ~/webshot
$ cd ~/webshot
$ npm install webshot
$ vim test.js
var webshot = require('webshot');
webshot('tw.yahoo.com', 'yahoo.png', function(err) {
if(err)
       console.log(err);
} );
$ nodejs test.js


然而,無法顯示中文字,簡言之就是缺字型,安裝一下即可:

$ sudo apt-get install xfonts-wqy

成果一切正常:

2014年2月26日 星期三

[Linux] 使用 Node.js 與 MongoDB 溝通與帳號認證 @ Ubuntu 12.04

透過 apt-get 安裝 node.js:

$ sudo apt-get install python-software-properties
$ sudo add-apt-repository ppa:chris-lea/node.js
$ sudo apt-get update
$ sudo apt-get install nodejs


建立 project:

$ cd project
$ npm install mongodb
$ vim test.js
var Db = require('mongodb').Db,
        MongoClient = require('mongodb').MongoClient;

var db_host = 'localhost';
var db_port = 27017;
var auth_db = 'admin';
var auth_name = 'account';
var auth_pass = 'password';

MongoClient.connect("mongodb://"+auth_name+":"+auth_pass+"@"+db_host+":"+db_port, function(err, db) {
        console.log(err);
        console.log(db.databaseName);
        db.close();
});

MongoClient.connect("mongodb://"+db_host+":"+db_port, function(err, db) {
        db.authenticate(auth_name, auth_pass, function(err, ret) {
                console.log(err);
       console.log(db.databaseName);
                console.log(ret);
       db.close();
        });
});
$ node test.js
Failed to load c++ bson extension, using pure JS version
null
admin
null
admin
true


以上為兩種跟 mongodb 進行認證的方式,一種是寫在 URI 中,另一種則是透過 db.authenticate 方式,至於這份程式的輸出不見得 "true" 是在最後一列,也有可能在第三列,這是因為 node.js async 架構關係,在這份 code 中不保證哪個 function 先跑。

如果 mongodb 本身要求認證,而未認證則會有錯誤訊息:

{ [MongoError: unauthorized] name: 'MongoError', ok: 0, errmsg: 'unauthorized' }

比較疑惑的地方是對於 auth_db 的部分,之前在用 pymongo 時,或是直接用 mongo 指令時,必須透過指定 authenticationDatabase 才能通過認證,但在 node.js 中,似乎沒有問題?再來摸看看好了。