這個練習可以分成三個部分
- 第一個部分是測試 ISBN查詢
- 第二個部分是將查詢結果寫入Google 試算表中
- 第三個部份是使用AppSheet 建立APP,透過APP掃描書籍ISBN條碼取得書籍資料
備註
AppSheet 免費版不能發布,如果只是要個人使用的話
可以在手機安裝 AppSheet,就可以用測試的方式執行APP
第一個部分是測試 ISBN查詢
可以透過修改以下連結的ISBN在全國圖書書目資訊網取得需要的書籍資料
http://192.83.186.170/search*cht?/i9789863123453/i9789863123453/0,0,0/frameset&FF=i9789863123453
第二個部分是將查詢結果寫入Google 試算表中
在實際利用Google Apps Script進行處理之前,先用 Excel 試試看取得的資料
Excel的”從Web”有兩種,一種是會開啟瀏覽器的(2019以後的版本要另外開啟-舊版匯入精靈);另一種是開啟Power Query
前者可以從瀏覽器選擇需要的資料表格
從資料→從Web
從 錄製巨集的VBA程式碼可以知道全國圖書書目資訊網的資料是在第5個表格
後者雖然同樣可以抓到資料,但是在導入Power Query時
抓取到的卻是第1個表格,不清楚這過程是怎麼處理的
兩種方式都可以順利抓到資料,也得知有的tr之中,如果第1個td是空白無資料
表示第2個td的內容是接續前一個tr的第2個td
這都有助於後面Google Apps Script的程式編寫
備註
Html的表格結構為
table 整個表格
-tr 資料列
-td 資料列的資料欄
因為目標是透過 AppSheet使用手機相機掃描ISBN條碼取得書籍資料
這跟直接在試算表輸入ISBN的方式會有差異
主要差異在”資料的最後一筆”代表什麼
直接輸入ISBN的方式,是要將資料放在目前已有的資料之後
因此新增的資料會放在”最後一筆”+1的列數
(當然也可以在寫入ISBN之後,再重新取得”最後一筆”的列數)
然後將搜尋到的書籍資料寫入其他欄位
而透過AppSheet輸入ISBN
因為在Google Apps Script設定試算表有資料寫入之後才會觸發程式
所以”最後一筆”的列數會是新增ISBN所寫入的資料列
接下來是處理透過UrlFetchApp.fetch()抓回來的網頁資料
由於Google Apps Script不能解析外部網頁的DOM
不過可以透過外加資料庫 Cheerio 的方式來取得目標表格的資料
取得資料之後就是進行資料清理
處理沒有資料的td
if(rTr.find('td').eq(0).text() !=="" ){ // 如果 第1個td是空值 表示是接續前一個tr的內容 如果不是空值 表示是新的項目 rTd1= rTr.find('td').eq(0).text().trim(); //取出第1個td 用 text()取值 trim()刪除空白格 rTd2= rTr.find('td').eq(1).text().trim(); //取出第2個td 用 text()取值 trim()刪除空白格 }else{ rTr2 = rowTable.find('tr').eq(i-1); rTd1= rTr2.find('td').eq(0).text().trim(); //前一列的第1個td rTd2= rTr2.find('td').eq(1).text().trim() +"/"+ rTr.find('td').eq(1).text().trim(); //連接目前列的第2個td 跟前一列的第2個td } book1[i]=rTd1; book2[i]=rTd2;
因為我是將tr中空白的第1個td,用前一列的第1個td取代
第2個td跟前一列的td合併
所以前一列的資料就會變成重複,而且是不完整的資料
處理重複的tr是透過 lastIndexOf找出重複、以及是最後一個陣列元素的索引值
當這個索引值等於目前陣列的索引值,表示跳過前面相同名稱的陣列元素 或者本身是沒有重複的陣列元素
再將 資料欄位 跟 資料內容 分別儲存在新的陣列之中
var bookLabel=[]; //處理過的資料欄位 var bookData=[]; //處理過的資料內容 var j =0; //陣列的index for ( var r=0 ; r < book1.length ; r++){ if(book1.lastIndexOf(book1[r]) == r){ //因為前面已經將空白的第1個td賦予前一列的第1個td值 並且在第2個td合併前一列的值,等於會有相同的第1個td值 //給定元素(book1[r]於陣列中最後一個被找到之索引值 如果等於目前陣列索引值 表示跳過前面相同名稱的元素 或者本身是沒有重複的元素 bookLabel[j]=book1[r]; bookData[j]=book2[r]; j = j +1; } }
完整的程式碼如下
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 |
function isbnQuery2() { //var bI = Browser.inputBox("請輸入ISBN"); //Logger.log(bI); //Logger.log(typeof(bI)); var sh= SpreadsheetApp.getActiveSpreadsheet(); var sOne =sh.getSheets()[0]; //取得有資料的最後一列數 var lr = sOne.getLastRow(); //Logger.log(lr); //取得有資料的最後一欄數 var lc = sOne.getLastColumn(); //sOne.getRange(lr,1).setValue(bI); var isbn = sOne.getRange(lr,1).getValue(); //9789861755267 9789865026578 var url = "http://192.83.186.170/search*cht?/i"+ isbn +"/i"+ isbn +"/0,0,0/frameset&FF=i"+ isbn; //Logger.log(url); var hData = UrlFetchApp.fetch(url); var rowData = hData.getContentText(); //處理網頁DOM var $ = Cheerio.load(rowData,{ decodeEntities: false }); //https://github.com/tani/cheeriogs //Logger.log($.html()); //Logger.log($('table').eq(4).text()); var rowTable = $('table').eq(4); //第5個表格 //Logger.log(rowTable.find('tr').length); //tr的筆數 var book1=[]; //存資料欄位 var book2=[]; //存資料內容 for (var i=0 ; i <rowTable.find('tr').length ;i++){ var rTr = rowTable.find('tr').eq(i); //第 i 個 tr var rTd1; //第1個td var rTd2; //第2個td //var book = {}; //Logger.log(rTr.find('td').eq(0).text()); if(rTr.find('td').eq(0).text() !=="" ){ // 如果 第1個td是空值 表示是接續前一個tr的內容 如果不是空值 表示是新的項目 rTd1= rTr.find('td').eq(0).text().trim(); //取出第1個td 用 text()取值 trim()刪除空白格 rTd2= rTr.find('td').eq(1).text().trim(); //取出第2個td 用 text()取值 trim()刪除空白格 }else{ rTr2 = rowTable.find('tr').eq(i-1); rTd1= rTr2.find('td').eq(0).text().trim(); //前一列的第1個td rTd2= rTr2.find('td').eq(1).text().trim() +"/"+ rTr.find('td').eq(1).text().trim(); //連接目前列的第2個td 跟前一列的第2個td } //Logger.log(rTd1+"--"+rTd2); book1[i]=rTd1; book2[i]=rTd2; } //Logger.log(book1); //Logger.log(book2); //Logger.log(book1.lastIndexOf(book1[2])); //Logger.log(book1.length); //處理重複的資料欄位 var bookLabel=[]; //處理過的資料欄位 var bookData=[]; //處理過的資料內容 var j =0; //陣列的index for ( var r=0 ; r < book1.length ; r++){ if(book1.lastIndexOf(book1[r]) == r){ //因為前面已經將空白的第1個td賦予前一列的第1個td值 並且在第2個td合併前一列的值,等於會有相同的第1個td值 //給定元素(book1[r]於陣列中最後一個被找到之索引值 如果等於目前陣列索引值 表示跳過前面相同名稱的元素 或者本身是沒有重複的元素 //Logger.log(book1[r]+"-"+book2[r]); bookLabel[j]=book1[r]; bookData[j]=book2[r]; // Logger.log(bookLabel[j]); // Logger.log(bookData[j]); j = j +1; } } //Logger.log(bookLabel); //Logger.log(bookData); //測試用的 // var bookLabel = [["著者", "題名", "版本項", "出版項", "面數高廣", "國際標準書號" ]]; // var bookData = [["123", "456", "6", "7", "8", "99999999999" ]]; //如果欄位不完整 則取前兩項 if(bookData.length <6){ sOne.getRange(lr,2).setValue(bookData[0]); sOne.getRange(lr,3).setValue(bookData[1]); }else{ sOne.getRange(lr,2,1,6).setValues([bookData]); } } |
第三個部份是使用AppSheet 建立APP,透過APP掃描書籍ISBN條碼取得書籍資料
可以直接從試算表→工具→AppSheet建立應用程式
在”Data”可以設定資料表(連結的試算表)
View Column可以設定資料顯示的方式,以及資料的運算
因為目前APP只是用來讀取、傳出ISBN資料到Google試算表進行處理
然後再讀取、顯示Google試算表的資料
所以只用到資料顯示的設定功能
不同的UX的SHOW形式不同
目前APP使用”CARD”的樣式,可以再設定Layout呈現的資料
SHOW還可以設定在單筆資料要顯示的內容
LABEL可以設定優先顯示的資料,將其置頂
可以注意圖片中資料表SHOW的順序跟右邊模擬手機顯示項目順序的差異
接著是設定如何新增資料
因為是透過ISBN來搜尋書籍資料,其他資料都是讀取試算表
所以只有 ISBN欄位需要勾選 EDITABLE
除了手動輸入之外,勾選 SCAN就可以用手機相機掃描
這樣就可以透過手機照相機掃描 Qr Code或者二維條碼取得ISBN
存檔之後,APP會將資料寫入Google試算表
Google試算表再觸發程式抓取書籍資料
在APP主畫面重新整理同步試算表資料就會抓回搜尋到的書籍資料了
參考資料
GAS + Cheerio 分析網頁資訊寫入Google 試算表
初步認識AppSheet