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

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

2015年3月18日 星期三

[Linux] 透過 sed 將 MySQL Table 資料轉成 csv 格式 @ Ubuntu 14.04

印象中之前找資料時,有發現 MySQL Server 可以輸出 CSV 格式到本地端。但是,對於無法存取到 DB Server 的檔案系統的環境時(AWS RDS),只好手動處理一下 :P

用法:

$ mysql -h rds-server -u root -p -e "SELECT field1, field2 FROM db_name.table_name" | grep "^Keyword" | sed 's/\t/","/g' | sed 's/^/"/g' | sed 's/$/"/g' > db-table-export.csv

其中 grep 那段只是為了mysql 輸出時,略過第一行 XD

2014年7月23日 星期三

iOS 開發筆記 - 使用 GCDAsyncSocket / AsyncSocket 之處理 Partial Data 的用法

原本想寫寫 BSD Socket 的,再搭配 dispatch_async 其實也能運作的很好,但讓我想起去年底面試一間新創時,被問到有沒有用過 GCDAsyncSocket 的心得,看來用用已經常見的 framework / library 已是一種流行的或組織文化吧?至少接手的人可以有一樣的思維而不用再去認識另一個 coding style 吧!

把玩 GCDAsyncSocket 時,一開始試用了 readDataToData 函數,但發現這東西不是我想要的,也就是 server 沒有吐出預設字元(newline)時,程式端無法處理已接收的資料,但可以得知已經接收多少 Partial Data (可用在回報下載、上傳進度)。爾後用一些關鍵字(partial data)找找,發現也有人有這種需求:Is there a way to get partial data read and clear the buffer?

原來會有這種困惑只是不懂 GCDAsyncSocket 設計架構,搞懂後就一切安好啦。

There are 3 methods of reading data using GCDAsyncSocket / AsyncSocket:
  • readDataWithTimeout : reads chunks of data as they arrive
  • readDataToLength : read a specific length of data before returning result
  • readDataToData : read until finding a specific terminator

對於不能確定 Server 回傳的資料長度或特徵的行為,就適合採用 readDataWithTimeout 方式 :)

- (GCDAsyncSocket*)asyncSocket {
if(!_asyncSocket)
_asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_queue_create("socket", NULL)];
return _asyncSocket;
}

- (NSMutableData *)buffer {
if (!_buffer) {
_buffer = [[NSMutableData alloc] init];
}
return _buffer;
}

- (void)viewDidLoad {
[super viewDidLoad];

if (![self.asyncSocket connectToHost:@"ServerIP" onPort:80 error:&error]) {
NSLog(@"ERROR: %@", error);
}
}

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
NSLog(@"didConnectToHost");
[sock readDataWithTimeout:-1 buffer:self.buffer bufferOffset:[self.buffer length] tag:1];
}

- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag {
NSLog(@"didReadPartialDataOfLength");
}

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(@"didReadData(%lu): %@", tag, @([data length]));
[self.buffer setLength:0];
[sock readDataWithTimeout:-1 buffer:self.buffer bufferOffset:[self.buffer length] tag:tag];
}

- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
NSLog(@"socketDidDisconnect: %@", err);
}

- (void)socketDidSecure:(GCDAsyncSocket *)sock {
NSLog(@"socketDidSecure");
}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
NSLog(@"didWriteDataWithTag");
}