Mechanizeでサイトログインしてスクレイピングするときのアレコレ メモ


## デバックで使えるメソッドMechanizeメソッド集

### Mechanizeクラスのメソッド

agentのページ遷移の履歴表示

```ruby
p @agent.history

# [https://sample.com/page1.html, https://sample.com/page1.htm2]
```


### Mechanize::Pageクラスのメソッド

ページのステータスコード表示

```ruby
p page.code

# "200"
```


### HTTP通信関連の表示

リクエスト、レスポンスヘッダ情報などをロガーで標準出力できる
特に、Mechanize内部で、FormのPOSTで送信されるqueryが見えるので、デバッグで重宝する

```ruby
require 'logger'

@agent = Mechanize.new
@agent.log = Logger.new $stderr
```


レスポンスヘッダー取得

```ruby
pp page.header

{"cache-control"=>"private",
 "content-type"=>"text/html; charset=utf-8",
 "x-aspnet-version"=>"Secret",
 "x-frame-options"=>"sameorigin",
 "date"=>"Tue, 05 Apr 2016 07:43:08 GMT",
 "content-length"=>"13577",
 "set-cookie"=>
  "cookie_oishii=1234.0000.4564; expires=Tue, 05-Apr-2016 08:49:09 GMT; path=/, hontoni_oishi=20220.0000; expires=Tue, 05-Apr-2016 08:49:09 GMT; path=/"}
```

リクエストヘッダの指定

```ruby
@agent.request_headers = {
  'Origin' => 'https://michadameyo.com',
  'accept-language' => 'ja,en-US;q=0.8,en;q=0.6,zh-CN;q=0.4,zh;q=0.2',
  'Upgrade-Insecure-Requests' => '1',
  'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
}
```

### 特定のcookieのvalueの取得・保存・追加
```ruby
uri = URI.parse("site.com")

# クッキー取得
cookie_str = @agent.cookie_jar.jar["site.com"]["/"]["hoshii_cookie_name"].value
File.write(CAPTCHA_PATH + '/cookie.txt', cookie_str)

# クッキー追加
cookie_str = File.read(CAPTCHA_PATH + '/cookie.txt')
agent.cookie_jar << Mechanize::Cookie.new('hosii_cookie_name', uri.host, {:value => cookie_str, :domain => uri.host, :path=>"/"}) 
```

## ページ遷移・リソースの取得方法あれこれ

### URL指定でページ取得(GET)

```ruby
top_uri = 'https://www.webpage.com/index.html'
uri = URI.parse(top_uri)
top_page  = @agent.get(uri)
```

### URLとPOSTパラメータ指定でページ取得(POST)

```ruby
uri = 'https://samplesite.com'
response = @agent.post(uri, {
  area: 'kumamoto',
  region: 'mashiki',
  Date: '20160531'
})

doc =  Nokogiri::XML.parse(response.body)
pp doc.css('docroot > array')[0].text.split("\n")
```

同一のパラメータ名で複数のvalueをPOSTする場合は以下の様に配列でvalueを複数定義する

```
response = agent.post(uri, {
  params1: ['value1-1', 'value1-2'],
  params2: 'value2',
})
```

Mechanizeが送るPOST(content-type => application/x-www-form-urlencoded)のqueryを以下のログから確認すると、`"params1=value1-1&params1=value1-2&params2=value2"`の様にparams1のパラメータ名が送信されていることがわかる。

```
D, [2020-04-17T15:11:55.066392 #59898] DEBUG -- : query: "params1=value1-1&params1=value1-2&params2=value2"
I, [2020-04-17T15:11:55.066596 #59898]  INFO -- : Net::HTTP::Post: /
D, [2020-04-17T15:11:55.066614 #59898] DEBUG -- : request-header: accept-encoding => gzip,deflate,identity
D, [2020-04-17T15:11:55.066623 #59898] DEBUG -- : request-header: accept => */*
D, [2020-04-17T15:11:55.066630 #59898] DEBUG -- : request-header: user-agent => Mechanize/2.7.6 Ruby/2.5.3p105 (http://github.com/sparklemotion/mechanize/)
D, [2020-04-17T15:11:55.066638 #59898] DEBUG -- : request-header: accept-charset => ISO-8859-1,utf-8;q=0.7,*;q=0.7
D, [2020-04-17T15:11:55.066644 #59898] DEBUG -- : request-header: accept-language => en-us,en;q=0.5
D, [2020-04-17T15:11:55.066654 #59898] DEBUG -- : request-header: host => minaide_kudasai.com
D, [2020-04-17T15:11:55.066673 #59898] DEBUG -- : request-header: content-type => application/x-www-form-urlencoded
D, [2020-04-17T15:11:55.066680 #59898] DEBUG -- : request-header: content-length => 48
```

### クリックでページ遷移する

```ruby
list_page = top_page.link_with(text: '記事一覧ページ').click
list_page = top_page.link_with(href: '/list?src=ichiran').click
```

### 遷移先ページ内のFormを取得して、POSTするパラメータを追加し、送信する


```ruby
form = page.form_with(name: 'register')
form.action     = 'https://minaide_kudasai/login.php'
form.LoginName = 'ID'
form.Passwd    = 'PASS'
form.checkbox_with(:name => 'ChkBox').value = '1'

login_page = agent.submit(form)
```

input fieldの名前によっては、上記の記載ができない場合がある。
その場合はこんな感じで書くと良い。

```ruby
form = page.forms[0]
form.field_with(name: 'UserId').value   = 'ID'
form.field_with(name: 'Password').value = 'PASS'
login_page = agent.submit(form)

puts login_page.body
```

Formの送信ボタン(submitボタン）にFormパラメータのname,valueが設定されているときは、submitだけでは送信ボタンに付けられたFormパラメータがqueryに追記されないので、submitではなくボタンclickで処理する必要がある。

```ruby
form = page.forms[0]
button = form.button_with(name: 'login_button') 
login_page = @agent.submit(form, button)
```


### mechanizeが保持していないフォームフィールドを自分で追加する

```ruby
form.add_field!('date', '20160606')
```


### ユーザーエージェントを指定する

```ruby
@agent = Mechanize.new
@agent.user_agent_alias = 'Windows IE 11'
```

### ページのヒストリーを保持しない

```ruby
@agent = Mechanize.new
# バックボタンが使える最小個数を保持する
@agent.max_history = 2
```


### 画像ファイルなどをダウンロードする

```ruby
url = base_url + page.search('#LoginWebCaptcha > div.igc_CaptchaImageArea > img').attribute('src').value

@agent.get(url).save_as(CAPTCHA_PATH + "/latest_captcha.gif")
```

## 実例集

### アパレルECサイトで商品をカートに入れて、買い物を進める感じの一連の操作

```ruby
require 'mechanize'
require 'pp'

# ログインページ取得
agent = Mechanize.new
agent.max_history = 2
page  = agent.get('https://minaide_kudasai/login.php')
page.encoding = 'Shift_JIS'

# ログイン処理
form = page.form_with(name: 'register')
form.action     = 'https://minaide_kudasai/dologin.php'
form.LoginName = 'ID'
form.Passwd    = 'PASS'
form.checkbox_with(:name => 'ChkBox').value = '1'


login_page = agent.submit(form)
login_page.encoding = 'Shift_JIS'

# トップページへ遷移
top_page = login_page.link_with(text: "トップへ").click

# トップページに表示されたランキング3位の商品ページへ遷移(1-2位は在庫が無かったので3位)
item_page_url = top_page.search('#container > ul:nth-child(2) > li:nth-child(3) > a').attribute('href').value
item_page = top_page.link_with(href: item_page_url).click

# 商品ページ内のカートに入れる
item_id = item_page.search('#container > table tr:nth-child(3) > td:nth-child(3) > div > span').attribute('id').value

cart_in_form = item_page.form_with(name: 'cart_form')
cart_in_form.qId = item_id
cart_in_form.qRetId = item_id
cart_in_form.qMode = 'store'
cart_in_form.qNum = 1

cart_page = agent.submit(cart_in_form)

puts cart_page
```


### 気づき

- Formのパラメータやリクエストヘッダ（特にCookie）をあれこれ手動で設定したくなるが、意外なほどMechanizeは普通のブラウザと同じように振る舞って、情報を集めてくれる。  
  本当に追加が必要な情報かどうか、しっかり調べてから、必要なForm、リクエストヘッダのみ追記するようにすること。  
  （特に、FormはブラウザとMechanizeのForm 生queryを比較すれば、過不足は明確に分けられるので。）