pcwu's TIL Notes


[JS] Leo.org 爬蟲筆記

最近想做小東西,需要查詢德文字的英文意思,最好還可以來個發音。於是看上了Leo.org 的 API。

當然人家是禁止 CORS,所以無法直接前端要資料,要透過 server 來爬。

偷研究了一下,發現手機版的網頁是透過呼叫 API 來取資料,bingo!

拿一個複詞來舉例:Briefe 點我開啟XML

http://pda.leo.org/dictQuery/m-vocab/ende/query.xml?tolerMode=nof&lp=ende&lang=de&resultOrder=basic&multiwordShowSingle=on&sectLenMax=16&search=Briefe

是一個 XML 檔案,但為了方便處理,利用了 nashwaan/xml-js 將它轉成 JS object:

const enLeoJson = word => (
  new Promise((resolve, reject) => {
    request({
      url: `http://pda.leo.org/dictQuery/m-vocab/ende/query.xml?tolerMode=nof&lp=ende&lang=de&resultOrder=basic&multiwordShowSingle=on&sectLenMax=16&search=${word}`,
      method: 'GET',
    }, (e, r, b) => {
      const data = xml2js(b, { compact: true, spaces: 2 });
      if (data) {
        resolve(data);
      } else {
        reject('WORD NOT FOUND!');
      }
    });
  })
);

檢查有沒有查到字

以下說明請透過 示範XML 來看,透過 XML 可以看得出來最上面的 search 內含有 hitcount,如果是零代表沒查到。

data.xml.search._attributes.hitcount

英文跟德文分別是 hitWordCntLefthitWordCntRight

Section & Entry

再來就是它有很多個 sectionentry , 一個 section 代表一個詞類, 一個 entry 代表一個詞意,我們只取最常見的用法,所以就是 section[0].entry[0],但要注意當只有一個 sectionentry 時 會是 section.entry[0]section[0].entry 之類的。

詞類:

data.xml.sectionlist.section[0]._attributes.sctTitle

entry 有一個 attributes 叫 langlvl 有分 A 跟 B,看了一下字的感覺應該是 b=basic a=advance,每個 entry 可取到詞性。

詞性:

data.xml.sectionlist.section[0].entry[0].info.category._attributes.type

Side

每個 entry 固定有2個 side 第一個是英文 第二個是德文。

詞的原始樣貌:

data.xml.sectionlist.section[0].entry[0].side[1].words.word._text

名詞會自動變回單數加上冠詞:Briefe => der Brief

動詞會自動變回原型:schlaft => schlafen

Audio

英文檔名:

data.xml.sectionlist.section[0].entry[0].side[0].ibox.pron._attributes.url

德文檔名:

data.xml.sectionlist.section[0].entry[0].side[1].ibox.pron._attributes.url

音檔連結:

http://pda.leo.org/media/audio/${url}.ogg

示範:das Auto

結合

最後長這樣,很醜,之後再用漂亮一點的方式重寫,先擋一下:

if (data && data.xml.search._attributes.hitcount > 0) {
  const section = data.xml.sectionlist.section[0] ? data.xml.sectionlist.section[0] : data.xml.sectionlist.section;
  const entry = section.entry[0] ? section.entry[0] : section.entry;

  const de = entry.side[1].words.word[0] ? entry.side[1].words.word[0]._text : entry.side[1].words.word._text;
  const en = entry.side[0].words.word[0] ? entry.side[0].words.word[0]._text: entry.side[0].words.word._text;
  const deAudio = entry.side[1].ibox.pron ? entry.side[1].ibox.pron._attributes.url : '';
  const enAudio = entry.side[0].ibox.pron ? entry.side[0].ibox.pron._attributes.url : '';
  const type = entry.info.category._attributes.type;

Reference