Gg / App 從Google Spreadsheets建立查詢ISBN的AppSheet APP

這個練習可以分成三個部分

  • 第一個部分是測試 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