TypeScript で cheerio-httpcli よく使う処理メモ。ログイン後、submitしてCSVダウンロードするとか。

たまに書くといつも変なとこにつまづくので、自分用メモ
書き終わって気づいたけど、require のところと、undefinedのハンドリング以外、
node.js版とほとんど変わらんかった。

## WEBアクセス編
### 基本形

GoogleでTypeScriptで検索する例

```typescript
import client = require("cheerio-httpcli");

client.fetch("https://www.google.com/search", {q: "typescript"}, function(err, $, res, body){
    console.log($('title').text());
});
```
第二引数の連想配列は、アクセス時のGetパラメータ。省略可能

### URLとか外だし
```typescript
import client = require("cheerio-httpcli");
const googleUrl : string = 'https://www.google.com/search'
let getParam : {q: string} = {q: "typescript"}

client.fetch(googleUrl, getParam, function(err, $, res, body){
    console.log($('title').text());
});
```

### Callbackしたくない人用
```typescript
import client = require("cheerio-httpcli");
const googleUrl : string = 'https://www.google.com/search'
let getParam : {q: string} = {q: "typescript"}

let response: client.Promise = client.fetch(googleUrl, getParam) 
response.then(function(result: client.FetchResult){
    console.log(result.$('title').text());
})
```
型推論されるので、client.Promiseはあってもなくてもよい。

### 同期アクセス
```typescript
import client = require("cheerio-httpcli");
const googleUrl : string = 'https://www.google.com/search'
let getParam : {q: string} = {q: "typescript"}

let result : client.FetchResult = client.fetchSync(googleUrl, getParam)
console.log(result.$('title').text());
```
公式曰くめっちゃ重いから、`fetchSync`は避けたほうがいいらしい

### 同期アクセス（Async-Await版）
```typescript
import client = require("cheerio-httpcli");
const googleUrl : string = 'https://www.google.com/search'
let getParam : {q: string} = {q: "typescript"}

async function scraping(){
    let result = await client.fetch(googleUrl,getParam)
    console.log(result.$('title').text());
}
scraping()
```
`client.fetch`はcallbackを省略するとPromise型になるので、Async-Awaitでも書ける

### ページA ⇒ ページBみたいな遷移

Googleで`typescript`で検索して一番上に表示されるページにさらに遷移

```typescript
import client = require("cheerio-httpcli");
const googleUrl : string = 'https://www.google.com/search'
let getParam : {q: string} = {q: "typescript"}

client.fetch(googleUrl,getParam)
.then(function(result: client.FetchResult){
    //(1) google検索直後
    let nextPageUrl: string = result.$('div.r > a').eq(0).attr('href') || '';
    return client.fetch(nextPageUrl)
})
.then(function(result: client.FetchResult){
    //(2) 掲載1位にアクセス後
    console.log(result.$('title').text())
})
.catch(function(err){
    console.log(err)
})
.finally(function(){
    console.log('終わりました')
})
```
callback関数を省略するとPromise型になるので、Returnすると、thenで繋げられる。

`result.$('div.r > a').eq(1).attr('href')` は型が `String || Undefined`となるが、
`client.fetch`のURLは、`string`しか受け付けないので、ハンドリングする必要がある
※なお上記の例は、ハンドリングとしては超不適切というかハンドリングか？っていう

### リンクが相対パスで書かれている場合のアクセス

```typescript
import client = require("cheerio-httpcli");
const googleUrl : string = 'https://www.google.com/search'
let getParam : {q: string} = {q: "typescript"}

client.fetch(googleUrl,getParam)
.then(function(result: client.FetchResult){
    return result.$('a.f9UGee').eq(3).click();
})
.then(function(result: client.FetchResult){
    console.log(result.$('title').text())
})
.catch(function(err){
    console.log(err)
})
.finally(function(){
    console.log('終わりました')
})
```
どうせ画面遷移するなら、リンクの絶対・相対を考慮しなくてよい、`click()`でやるか、
後述の`url()`を使うべし

### submitしたい場合の画面遷移

```typescript
import client = require("cheerio-httpcli");
const loginUrl : string = 'https://*****/login'

client.fetch(loginUrl)
.then(function(result: client.FetchResult){
    //(1) ログイン画面アクセス後
    let form = result.$('form[name=login]');
    form.field({
        uid: "******"
        password: "*****" ,
    });
    return form.find('input[type=submit]').click();
})
.then(function(result: client.FetchResult){
    //(2) ログイン成功 or 失敗後
    console.log(result.$('title').text())
})
.catch(function(err){
    console.log(err)
})
.finally(function(){
    console.log('終わりました')
})
```
formエレメントを探してきて、.field()で項目セットしつつ、submitボタン探してclick()を実行する。
click()は引数にcallbackを実装できるが、省略するとPromise型の戻りになる。

## セレクタ編

基本的にjQueryライクに指定する

```typescript
console.log(result.$('div.contents').text());
console.log(result.$('div > a').attr('href'));
```

いくつかのフィルタは使えるが

```typescript
console.log(result.$('div:empty').text());
console.log(result.$('div:contains("hoge")').text());
```

いくつかのフィルタは、メソッドチェーンで記載する必要がある。

```typescript
//console.log(result.$('div:eq(3)').text());  これはSyntaxエラーになる
//console.log(result.$('div:first').text());  これもSyntaxエラーになる
console.log(result.$('div').eq(3).text()); 
console.log(result.$('div').first().text());
```

属性にアクセスするときもjQueryライクに

```typescript
console.log(result.$('div a').eq(0).attr('href')); 
```

なお、リンクや画像タグは、`URL()`で相対パスを、絶対パスに変換してくれる

```typescript
console.log(result.$('div a').eq(0).url()); 
```

画像のURLが src属性じゃなく、data-srcなどに記載されている場合、
以下のように、`url()`の引数に属性名を記載するとそっちのパスで調整してくれる。

```typescript
console.log(result.$('img').eq(0).url('data-src')); 
```
## その他

referrerとか、Cookieとかは勝手に設定してくれる
個別に設定したい場合は、`client.set`を使う。詳細は[公式](https://www.npmjs.com/package/cheerio-httpcli#setname-value-nomerge)へ

ダウンローダも用意されているけど、imgタグしかダウンロードできないので、
よくある、サムネにリンク貼って、大きい画像はこちらみたいなサイト構成のときは、
独自に`request`つかって実装する必要がある。
その時、request側でもcookieを引き継ぐ必要がある場合は、
`result.response.headers["set-cookie"]`で配列でCookieが取得できるので、[^1]
`request`にはこんな感じで渡してあげる[^2]

[^1]: ちなみに、あくまでset-cookieなので、セミコロン区切りで、expiresやsecure,pathとかがくっつくから、はがしたうえで、渡してあげましょう
[^2]: ちなみに、`request({url: 'http://****',header: {'Cookie': cookie}, ・・・)` というやり方は、requestモジュールの正当な方法論じゃないらしい

```typescript
import request = require('request');

const jar = request.jar();
jar.setCookie(key, value);

request('https://********', { jar: jar },function(error, response, body){
    // なんかかんか
});
```


あと、送信ボタン押すと、CSVとかがダウンロードできる系のWEBサイトは、
bodyをShift_JIS変換して、`fs.writeFile`すればOK

```typescript
import client = require("cheerio-httpcli");
import fs = require('fs');
import iconv = require('iconv-lite');

const url : string = 'https://***********/'

client.fetch(url)
.then(function(result: client.FetchResult){
    let form = result.$('form[name=****]');
    return form.find('input[type=submit]').click();
})
.then(function(result: client.FetchResult){
    fs.writeFileSync('hoge.csv',iconv.encode(result.body, "Shift_JIS"));
})
.catch(function(err){
    console.log(err)
})
.finally(function(){
    console.log('終わりました')
})
```