初試 JavaScript

(暫時不知道影音網站要寫哪部份, 從最近在練習的 JavaScript 講一下心得.)

JavaScript 剛開始是由 NetScape 開發, 後來發展成 ECMA 通過的共同標準.

而一般網站結構, 會大約分為前端跟後端, 前端就包括:
HTML 負責網頁結構, 基本內容, 提交表格(form)等.
CSS 負責版型, 樣式.
JavaScript 則負責屬於前端的運算, 邏輯判斷.

雖然 JavaScript 也有在伺服器執行的方式, 不過伺服器端有更多競爭者如: PHP / ASP , 近期還有 Python 等, 所以大多應用於前端.

(稍微跳離一下)
JavaScript , PHP , MariaDB 雖然都有字串處理, 基本的迴圈, 邏輯判斷等功能, 不過使用的時機與要處理的資料來源不同, MariaDB 是處理資料庫存放的資料為主, JavaScript 是透過 http(s) 向 Web 取得資料, 然後在 Client 端運作, PHP 則介於兩者之間, 包括存取資料庫, 也包括接受 Client 端的表格(form)或 Request (如: GET/POST ).

所以除了寫程式的語法不同, 適用的資料也不同, 比如:
1. 消費者輸入訂單, 未成立訂單時, 可以在前端用 JavaScript 試算.
2. 查詢消費者累計消費金額時, 可以直接用資料庫的 select sum 算出總值.
3. 因為某些促銷, 依過去的特定消費紀錄, 計算是否符合優惠, 因為優惠不適合放前端, 可能會被直接修改, 或者需要資料庫的資料, 這時候就需要 PHP 這類程式來處理.

雖然之前用 PHP 寫一個統計系統, 因為環境單純, 都在區網速度快, 所以前端只有使用 HTML 的 form , 然後 PHP 整理後重新顯示, 使用一些 Session 變數來判斷目前顯示的內容, 所以還沒有從頭寫過 JavaScript .... XD

(跳回來)

因為寫 PHP 已經有 物件 的概念, 練習 JavaScript 並不算太大的問題, 基本的語法結構, 判斷式寫法先了解, 然後開始了解如何接收 HTML 的資料, 進行加工, 然後學一些字串判斷, 分割字串等.

目前覺得需要經驗累積的, 是 JavaScript 對變數的處理, 因為 JavaScript 支援物件, 陣列, 傳統變數等型態, 而且 JavaScript 會自動判斷目前最適合的型態, 所以做一個動作之後, 要確認回傳的型態是什麼, 要進一步處理時, 要看看 JavaScript 是否會自動轉換, 或假如輸入意外的資料時, 是否會造成意外的錯誤.

所以練習到這邊, 反而在要處理的資料上, 加了好幾道關卡, 以免意外的錯誤發生.

就這樣, 連續幾天等行政程序的空檔, 練習的心得.

影音網站網址特性....

前一篇有提到要抓網站來分析出 pattern :
http://slimetw.blogspot.com/2018/06/pattern.html

既然要分析, 就要把資料存在合適的地方, 現在關聯式資料庫很容易取得跟安裝, 就分析的網頁結構來說, 不存整部影片的話, 硬碟記憶體空間也很大.

既然要存 資料 , 就要大約知道有哪些資料, 要如何分類, 正規化才能依這些特性進行.

回到原本的主題: 影音網站的自動抓取. 因為不是抓影片本身, 而是抓連結, 再從連結內容分析出 pattern , 再找出可能相關的連結, 排除廣告, 再給程式繼續抓.

所以最關鍵的就是網站連結, 一般會使用 URL 規格, 雖然 URL 基本上不會重複, 但是對影音網站來說, 常常會有配合廣告商, 在連結後面加上廣告商或某些分析用的變數, 例如: /?ref=2397 或 /embedded=xvideo

所以連結可能看起來好幾個, 卻都是同一個影片, 或者同一個業者用了多個 Domain Name , 其實結構都是一樣的, 或者新廠商 A 向廠商 B 致敬, 網址結構相同, 而內容卻不同.

因為這種特性, 所以網址本身必須"純化", 包括幾個步驟:
1. 去除 URL 多餘的變數.
2. 影音網站的網址結構區分:
a. 單一影片.
b. 影片組 ( playlist 或 favorite )
c. 網站整理好的如: 依女優或其他類型列表.
d. 搜尋類型.
3. 再依上面的結構, 分別拆出不同的資料:
a. 影片在該網站內的序號(必存), 影片名稱( 8 成有, 但很多名稱是亂打的), 番號( 3 成有)
b. 通常只有 playlist 名稱或 tag .
c. 這部份不同網站有不同的方式, 有些會編序號, 有些網站只用羅馬拼音, 如果是羅馬拼音就要再加工.
d. 有些網站會故意把影片切短, 然後用影片名稱去搜尋, 屬於影音網站特有的方式.

所以要分析網址, 就要先知道這些分類, 網址的 pattern 才有意義.

影片網站pattern初步分析

要做網站分析, 網站就要分析出結構, 找到 pattern .

而從單純到複雜的程度, 大約是:
1. 政府網站: 基本上不會有廣告, 結構也最單純.
2. 製造業的公司網站: 產品可能會變化, 但基本上不會有廣告, 結構也不太會變.
3. 遊戲網站: 雖然動畫可能很多, 廣告則大多是自家的.
4. Blog 等互動網站: 廣告很多, 大多是別人家的, 結構反而單純.
5. 影音網站: 別人家的廣告多, 自家的廣告也多, 彈跳視窗多.

既然要分析網站的 pattern , 抓取網站內容才能分析, 不過要分析之前, 就要先思考網站裡面有什麼東西.

以影音網站而言, 最重要的影片只會有一個, 就是正準備播放的影片.
但是超多廣告, 所以廣告也要分類, 如果連結是同一個網站, 那可能是自家的.
連結是不同網站則可能是別人家的.

但是這樣並不容易找到影片的本體, 所以還是要試著分開, 例如:

標題: 影片 A ; 影片 ; 影片簡介或分組 g1 ; 廣告(自家) * n1 ; 廣告(他家) * n2
標題: 影片 B ; 影片 ; 影片簡介或分組 g2 ; 廣告(自家) * n3 ; 廣告(他家) * n4

因為廣告可能有數量上的不同, 但仍有一些共通的特性, 就是標題通常是唯一且跟影片最接近的, 所以拆 pattern 時, 就可以先從標題下手, 找出標題大約被包在哪個 div 內.

而影音網站的廣告(自家), 往往就是其他的影片連結, 所以這邊抓出 pattern 後, 就有機會透過程式自動去把所有的連結找出來.

PHP + cURL 爬蟲基本語法

寫程式常常會聽到: 不要重複發明輪子! 或 站在巨人的肩膀上.
意思是已經有很多工具可以用的時候, 學習使用工具就好. (當然授權也要注意.)

如果要做一個網站分析的程式, 首要工作就是把網站的內容抓下來.
抓網站內容就有一個好工具: cURL , 基本上沒有授權問題, 而且 PHP 內已經有相關語法可以直接使用(註1).

在 PHP 內使用 cURL 的步驟, 大約是:
1. curl_init() 初始化一個連線.
2. curl_setopt() 指定相關參數.
3. curl_exec() 連線抓取網站資料.
4. curl_close() 關閉連線.

這樣好像不知道要抓什麼? 其實全部都在 curl_setopt() 內設定, 以常用的功能來看:

$ch = curl_init() ;
$url = 'https://slimetw.blogspot.com/' ;
curl_setopt( $ch , CURLOPT_URL , $url ) ;
curl_setopt( $ch , CURLOPT_HEADER , true ) ;
curl_setopt( $ch , CURLOPT_RETURNTRANSFER , true ) ;
curl_setopt( $ch , CURLOPT_FOLLOWLOCATION , true ) ;
$temp = curl_exec( $ch ) ;
curl_close( $ch ) ;
echo strlen( $temp ) ;

指定一個網址, 然後抓取時連同 header 都抓(註2), 用 CURLOPT_FOLLOWLOCATION 設定允許轉址, 然後用 CURLOPT_RETURNTRANSFER 設定回傳的資料不是直接顯示, 而是可以轉給其他地方用.

執行這個 PHP 之後, 就會把網站本身的內容長度顯示出來, 這當然不是主要的功能, 而是連線測試, 以後只有網址跟存放的地方會換掉, 就可以寫成函式(function)多次呼叫使用.

註:
1. XAMPP 7.2.6 包含的 PHP 已經內建.
2. header 是由網站主機提供給瀏覽器用的參考資料, 一般不會呈現給使用者看, 不過假如以後要分析可能會用到, 所以先抓.

用 MariaDB 切 URL 文字

字串的處理往往是因為應用上的需要,
而在"網站爬蟲"時, URL 常常是要分解的字串,
所以對 URL 的處理就有幾個部份:

A. 判斷是否為 URL :
以一般網站而言, 是 http:// 或 https:// 開頭, 後面至少有一個 / ,
所以可以用 正規表示式 來判斷, where url regexp '^https?://.*/.*$'

B. 如果是網頁 URL , 取出協定 :
因為預設 URL 是有 :// 字串, 所以可以用 position( in ) 來找到字串內的位置, 再用 left 剪裁.
比如: position( '://' in 'https://' ) = 6 表示 :// 從第 6 位開始(註1)
但是如果用 left( 'https://' , position( '://' in 'https://' ) ) 結果是 https:
雖然也是可以理解, 不過一般來說協定只要紀錄成 https , 所以長度要減 1 ,
就用 left( 'https://' , position( '://' in 'https://' ) -1 ) , 取得協定是 http 或 https

(註2)

另外的方法也可以用 regexp 判斷開頭是 https:// 就直接寫入 https 到指定欄位等作法.

C. 為了分析不同網站的 pattern , 所以取得 hostname 紀錄也是必要的,
比如: https://hostname/A/B/C 使用 / 區隔找出 hostname , 但左邊開頭有 // , 可能造成誤判,
所以分兩個階段, 先切成 https:// , hostname/A/B/C , 再取出 hostname ,
第一階段類似前一個步驟取協定的方式,
position( '://' in 'https://hostname/A/B/C' ) = 6
要取得 hostname/A/B/C , 原字串從右側取出總長度減去左側不要的字串長度
所以語法是: right( url , length( url ) - position( '://' in 'https://hostname/A/B/C' ) - 2 )
要 -2 的原因是搜尋 :// 時, 位置在 : , 如果沒有 -2 , 剩下的字串還會有 // , 盡量精簡就不留這個.

取得 hostname/A/B/C 之後, 再判斷一次 / , 就用
left(
right( url , length( url ) - position( '://' in 'https://hostname/A/B/C' ) - 2 ),
position(
'/' in
right( url , length( url ) - position( '://' in 'https://hostname/A/B/C' ) - 2 ) - 1
)
)

就可以取得 hostname , 存起來.
註1: MariaDB 字串位置從 1 開始, 有些軟體或資料庫字串會用 0 開始.
註2: 雖然大部分網址會用小寫字母, 不過有時候複製或轉換常有首字變大寫, 特別是經過 Office 軟體複製貼上, 所以比對時最好加上 lower() 處理.