yasuri 2.0.12 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: a4fed4a13bb125758515e3c0ced665b1ca3d20b6
4
- data.tar.gz: e9dfb2ed6256a367db2e5b6a78d23fa097c422d7
2
+ SHA256:
3
+ metadata.gz: a7bf438a08fc83fec7e78cb5543577c98f6cc98b4f5fae7b0dd969f2049c0531
4
+ data.tar.gz: e399c6b57589b7d8ba2e8eff7a1d204fa7f8e676f82f631057e19a9377333060
5
5
  SHA512:
6
- metadata.gz: 8b9d6345f3f49b1f7d9445ce18bca736b8cbeedc69979a45d541b59af4e09092d7c1d12886801a24296e9e3d73f39a7c2d53a7c2de12e1a0ff890623b47cfe84
7
- data.tar.gz: 6d755f266062052dd5244599deefefea85f7570c827a898e48eee22c44510dde287b0554ed2cae85e3b94b44fe4eb6f74b512c44047e6cc1bb43fe27a93143b0
6
+ metadata.gz: 56f39994972657712cb7d95e5ceaadefca8de41e06c2cd4759363b496d7c8531fad7517f9df99bf2446c144f01c5cd82cbc94146c432d6b5b552f092b975ecd7
7
+ data.tar.gz: cf74a25615187ecbe5f8ca5f2072679fa9cc1902dfa3bf2190b87e11104f332688cdaee16f4be6cb00f9ed63fa18f2ec8f27cf32b0b27389f81d98229fa212e6
@@ -0,0 +1,35 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ matrix:
22
+ ruby-version: ['2.6', '2.7', '3.0']
23
+
24
+ steps:
25
+ - uses: actions/checkout@v2
26
+ - name: Set up Ruby
27
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
28
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
29
+ # uses: ruby/setup-ruby@v1
30
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
31
+ with:
32
+ ruby-version: ${{ matrix.ruby-version }}
33
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
34
+ - name: Run tests
35
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -66,5 +66,4 @@ tramp
66
66
  # cask packages
67
67
  .cask/
68
68
 
69
- .ruby-version
70
- Gemfile.lock
69
+ Gemfile.lock
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.0
data/.travis.yml CHANGED
@@ -1,9 +1,7 @@
1
1
  language: ruby
2
- rvm:
3
- - 2.2.0
4
2
  script:
5
3
  - ruby --version
6
4
  - rspec spec
7
5
  addons:
8
6
  code_climate:
9
- repo_token: 0dc78d33107a7f11f257c0218ac1a37e0073005bb9734f2fd61d0f7e803fc151
7
+ repo_token: 0dc78d33107a7f11f257c0218ac1a37e0073005bb9734f2fd61d0f7e803fc151
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # Yasuri [![Build Status](https://travis-ci.org/tac0x2a/yasuri.svg?branch=master)](https://travis-ci.org/tac0x2a/yasuri) [![Coverage Status](https://coveralls.io/repos/tac0x2a/yasuri/badge.svg?branch=master)](https://coveralls.io/r/tac0x2a/yasuri?branch=master) [![Code Climate](https://codeclimate.com/github/tac0x2a/yasuri/badges/gpa.svg)](https://codeclimate.com/github/tac0x2a/yasuri)
1
+ # Yasuri
2
+ [![Build Status](https://github.com/tac0x2a/yasuri/actions/workflows/ruby.yml/badge.svg)](https://github.com/tac0x2a/yasuri/actions/workflows/ruby.yml)
3
+ [![Coverage Status](https://coveralls.io/repos/tac0x2a/yasuri/badge.svg?branch=master)](https://coveralls.io/r/tac0x2a/yasuri?branch=master) [![Maintainability](https://api.codeclimate.com/v1/badges/c29480fea1305afe999f/maintainability)](https://codeclimate.com/github/tac0x2a/yasuri/maintainability)
2
4
 
3
- Yasuri (鑢) is an easy web-scraping library for supporting "[Mechanize](https://github.com/sparklemotion/mechanize)".
5
+ Yasuri (鑢) is an easy web-scraping library for supporting "[Mechanize](https://github.com/sparklemotion/mechanize)", and CLI tool using it.
4
6
 
5
7
  Yasuri can reduce frequently processes in Scraping.
6
8
 
@@ -31,7 +33,10 @@ or
31
33
 
32
34
  ```ruby
33
35
  # for Ruby 1.9.3 or lower
34
- gem 'yasuri', '~> 1.9'
36
+ gem 'yasuri', '~> 2.0', '>= 2.0.13'
37
+
38
+ # for Ruby 3.0.0 or lower
39
+ gem 'yasuri', '~> 3.1'
35
40
  ```
36
41
 
37
42
 
@@ -44,6 +49,7 @@ Or install it yourself as:
44
49
  $ gem install yasuri
45
50
 
46
51
  ## Usage
52
+ ### Use as library
47
53
 
48
54
  ```ruby
49
55
  # Node tree constructing by DSL
@@ -52,32 +58,92 @@ root = Yasuri.links_root '//*[@id="menu"]/ul/li/a' do
52
58
  text_content '//*[@id="contents"]/p[1]'
53
59
  end
54
60
 
61
+
62
+ # Node tree constructing by YAML
63
+ src = <<-EOYAML
64
+ links_root:
65
+ path: "//*[@id='menu']/ul/li/a"
66
+ text_title: "//*[@id='contents']/h2"
67
+ text_content: "//*[@id='contents']/p[1]"
68
+ EOYAML
69
+ root = Yasuri.yaml2tree(src)
70
+
71
+
55
72
  # Node tree constructing by JSON
56
73
  src = <<-EOJSON
57
- { "node" : "links",
58
- "name" : "root",
59
- "path" : "//*[@id='menu']/ul/li/a",
60
- "children" : [
61
- { "node" : "text",
62
- "name" : "title",
63
- "path" : "//*[@id='contents']/h2"
64
- },
65
- { "node" : "text",
66
- "name" : "content",
67
- "path" : "//*[@id='contents']/p[1]"
68
- }
69
- ]
70
- }
74
+ {
75
+ "links_root": {
76
+ "path": "//*[@id='menu']/ul/li/a",
77
+ "text_title": "//*[@id='contents']/h2",
78
+ "text_content": "//*[@id='contents']/p[1]"
79
+ }
80
+ }
71
81
  EOJSON
72
82
  root = Yasuri.json2tree(src)
73
83
 
74
- agent = Mechanize.new
75
- root_page = agent.get("http://some.scraping.page.net/")
84
+ # Execution and getting scraped result
85
+ result = root.scrape("http://some.scraping.page.tac42.net/")
86
+ # => [
87
+ # {"title" => "PageTitle 01", "content" => "Page Contents 01" },
88
+ # {"title" => "PageTitle 02", "content" => "Page Contents 02" },
89
+ # ...
90
+ # {"title" => "PageTitle N", "content" => "Page Contents N" }
91
+ # ]
92
+ ```
93
+
94
+ ### Use as CLI
95
+
96
+ ```sh
97
+ # After gem installation..
98
+ $ yasuri help scrape
99
+ Usage:
100
+ yasuri scrape <URI> [[--file <TREE_FILE>] or [--json <JSON>]]
101
+
102
+ Options:
103
+ f, [--file=FILE] # path to file that written yasuri tree as json or yaml
104
+ j, [--json=JSON] # yasuri tree format json string
105
+ i, [--interval=N] # interval each request [ms]
76
106
 
77
- result = root.inject(agent, root_page)
78
- # => [ {"title" => "PageTitle", "content" => "Page Contents" }, ... ]
107
+ Getting from <URI> and scrape it. with <JSON> or json/yml from <TREE_FILE>. They should be Yasuri's format json or yaml string.
79
108
  ```
80
109
 
110
+ Example
111
+ ```sh
112
+ $ yasuri scrape "https://www.ruby-lang.org/en/" -j '
113
+ {
114
+ "text_title": "/html/head/title",
115
+ "text_desc": "//*[@id=\"intro\"]/p"
116
+ }'
117
+
118
+ {"title":"Ruby Programming Language","desc":"\n A dynamic, open source programming language with a focus on\n simplicity and productivity. It has an elegant syntax that is\n natural to read and easy to write.\n "}
119
+ ```
120
+
121
+ ## Dev
122
+ ```sh
123
+ $ gem install bundler
124
+ $ bundle install
125
+ ```
126
+ ### Test
127
+ ```sh
128
+ $ rake
129
+ # or
130
+ $ rspec spec/*spec.rb
131
+ ```
132
+
133
+ ### Test gem in local
134
+ ```sh
135
+ $ gem build yasuri.gemspec
136
+ $ gem install yasuri-*.gem
137
+ ```
138
+ ### Release RubyGems
139
+ ```sh
140
+ # Only first time
141
+ $ curl -u <user_name> https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials
142
+ $ chmod 0600 ~/.gem/credentials
143
+
144
+ $ nano lib/yasuri/version.rb # edit gem version
145
+ $ rake release
146
+ ```
81
147
 
82
148
  ## Contributing
83
149
 
data/USAGE.ja.md CHANGED
@@ -1,24 +1,32 @@
1
- # Yasuri の使い方
1
+ # Yasuri
2
2
 
3
3
  ## Yasuri とは
4
- Yasuri (鑢) は簡単にWebスクレイピングを行うための、"[Mechanize](https://github.com/sparklemotion/mechanize)" をサポートするライブラリです.
4
+ Yasuri (鑢) Webスクレイピングを宣言的に行うためのライブラリと、それを用いたスクレイピングのコマンドラインツールです。
5
+
6
+ 簡単な宣言的記法で期待結果を記述するだけでスクレイピングした結果を得られます。
5
7
 
6
8
  Yasuriは、スクレイピングにおける、よくある処理を簡単に記述することができます.
7
- 例えば、
9
+ 例えば、以下のような処理を簡単に実現することができます.
8
10
 
9
- + ページ内の複数のリンクを開いて、各ページをスクレイピングした結果をHashで取得する
10
11
  + ページ内の複数のテキストをスクレイピングし、名前をつけてHashにする
12
+ + ページ内の複数のリンクを開いて、各ページをスクレイピングした結果をHashで取得する
11
13
  + ページ内に繰り返し出現するテーブルをそれぞれスクレイピングして、配列として取得する
12
- + ページネーションで提供される各ページのうち、上位3つだけを順にスクレイピングする
13
-
14
- これらを簡単に実装することができます.
14
+ + ページネーションで提供される各ページのうち、最初の3ページだけをスクレイピングする
15
15
 
16
16
  ## クイックスタート
17
17
 
18
+ #### インストール
19
+ ```sh
20
+ # for Ruby 2.3.2
21
+ $ gem 'yasuri', '~> 2.0', '>= 2.0.13'
18
22
  ```
23
+ または
24
+ ```sh
25
+ # for Ruby 3.0.0 or upper
19
26
  $ gem install yasuri
20
27
  ```
21
28
 
29
+ #### ライブラリとして使う
22
30
  ```ruby
23
31
  require 'yasuri'
24
32
  require 'machinize'
@@ -29,83 +37,190 @@ root = Yasuri.links_root '//*[@id="menu"]/ul/li/a' do
29
37
  text_content '//*[@id="contents"]/p[1]'
30
38
  end
31
39
 
32
- agent = Mechanize.new
33
- root_page = agent.get("http://some.scraping.page.net/")
34
-
35
- result = root.inject(agent, root_page)
36
- # => [ {"title" => "PageTitle1", "content" => "Page Contents1" },
37
- # {"title" => "PageTitle2", "content" => "Page Contents2" }, ... ]
38
-
40
+ result = root.scrape("http://some.scraping.page.tac42.net/")
41
+ # => [
42
+ # {"title" => "PageTitle 01", "content" => "Page Contents 01" },
43
+ # {"title" => "PageTitle 02", "content" => "Page Contents 02" },
44
+ # ...
45
+ # {"title" => "PageTitle N", "content" => "Page Contents N" }
46
+ # ]
39
47
  ```
48
+
40
49
  この例では、 LinkNode(`links_root`)の xpath で指定された各リンク先のページから、TextNode(`text_title`,`text_content`) の xpath で指定された2つのテキストをスクレイピングする例です.
41
50
 
42
51
  (言い換えると、`//*[@id="menu"]/ul/li/a` で示される各リンクを開いて、`//*[@id="contents"]/h2` と `//*[@id="contents"]/p[1]` で指定されたテキストをスクレイピングします)
43
52
 
44
- ## 基本
45
53
 
46
- 1. パースツリーを作る
47
- 2. Mechanize の agent と対象のページを与えてパースを開始する
54
+ #### CLIツールとして使う
55
+ 上記と同じことを、CLIのコマンドとして実行できます。
56
+
57
+ ```sh
58
+ $ yasuri scrape "http://some.scraping.page.tac42.net/" -j '
59
+ {
60
+ "links_root": {
61
+ "path": "//*[@id=\"menu\"]/ul/li/a",
62
+ "text_title": "//*[@id=\"contents\"]/h2",
63
+ "text_content": "//*[@id=\"contents\"]/p[1]"
64
+ }
65
+ }'
66
+
67
+ [
68
+ {"title":"PageTitle 01","content":"Page Contents 01"},
69
+ {"title":"PageTitle 02","content":"Page Contents 02"},
70
+ ...,
71
+ {"title":"PageTitle N","content":"Page Contents N"}
72
+ ]
73
+ ```
48
74
 
75
+ 結果はjson形式の文字列として取得できます。
49
76
 
50
- ### パースツリーを作る
77
+ ----------------------------
78
+ ## パースツリー
51
79
 
52
- ```ruby
53
- require 'mechanize'
54
- require 'yasuri'
80
+ パースツリーとは、スクレイピングする要素と出力構造を宣言的に定義するための木構造データです。
81
+ パースツリーは入れ子になった Node で構成されます.Node は `Type`, `Name`, `Path`, `Childlen`, `Options` 属性を持っており、その `Type` に応じたスクレイピング処理を行います.(ただし、`MapNode` のみ `Path` を持ちません)
55
82
 
56
83
 
57
- # 1. パースツリーを作る
58
- tree = Yasuri.links_title '/html/body/a' do
59
- text_name '/html/body/p'
60
- end
84
+ パースツリーは以下のフォーマットで定義されます.
61
85
 
62
- # 2. Mechanize の agent と対象のページを与えてパースを開始する
63
- agent = Mechanize.new
64
- page = agent.get(uri)
86
+ ```ruby
87
+ # 1ノードからなる単純なツリー
88
+ Yasuri.<Type>_<Name> <Path> [,<Options>]
89
+
90
+ # 入れ子になっているツリー
91
+ Yasuri.<Type>_<Name> <Path> [,<Options>] do
92
+ <Type>_<Name> <Path> [,<Options>] do
93
+ <Type>_<Name> <Path> [,<Options>]
94
+ ...
95
+ end
96
+ end
97
+ ```
98
+
99
+ **例**
65
100
 
101
+ ```ruby
102
+ # 1ノードからなる単純なツリー
103
+ Yasuri.text_title '/html/head/title', truncate:/^[^,]+/
66
104
 
67
- tree.inject(agent, page)
105
+ # 入れ子になっているツリー
106
+ Yasuri.links_root '//*[@id="menu"]/ul/li/a' do
107
+ struct_table './tr' do
108
+ text_title './td[1]'
109
+ text_pub_date './td[2]'
110
+ end
111
+ end
68
112
  ```
69
113
 
70
- ツリーは、DSLまたはjsonで定義することができます.上の例ではDSLで定義しています.
71
- 以下は、jsonで上記と等価な解析ツリーを定義した例です.
72
114
 
115
+ パースツリーはRubyのDSL、JSON、YAMLのいずれかで定義することができます。
116
+ 以下は、上記と同じパースツリーをそれぞれの記法で定義した例です。
117
+
118
+ **Ruby DSLで定義する場合**
73
119
  ```ruby
74
- # json で構成する場合
75
- src = <<-EOJSON
76
- { "node" : "links",
77
- "name" : "title",
78
- "path" : "/html/body/a",
79
- "children" : [
80
- { "node" : "text",
81
- "name" : "name",
82
- "path" : "/html/body/p"
83
- }
84
- ]
85
- }
86
- EOJSON
87
- tree = Yasuri.json2tree(src)
120
+ Yasuri.links_title '/html/body/a' do
121
+ text_name '/html/body/p'
122
+ end
88
123
  ```
89
124
 
125
+ **JSONで定義する場合**
126
+ ```json
127
+ {
128
+ links_title": {
129
+ "path": "/html/body/a",
130
+ "text_name": "/html/body/p"
131
+ }
132
+ }
133
+ ```
90
134
 
91
- ### Node
92
- ツリーは入れ子になった *Node* で構成されます.
93
- Node は `Type`, `Name`, `Path`, `Childlen`, `Options` を持っています.
135
+ **YAMLで定義する場合**
136
+ ```yaml
137
+ links_title:
138
+ path: "/html/body/a"
139
+ text_name: "/html/body/p"
140
+ ```
94
141
 
95
- Nodeは以下のフォーマットで定義されます.
142
+ **パースツリーの特殊なケース**
96
143
 
144
+ rootの直下の要素が1つだけの場合、Hash(Object)ではなく、その要素を直接返します。
145
+ ```json
146
+ {
147
+ "text_title": "/html/head/title",
148
+ "text_body": "/html/body",
149
+ }
150
+ # => {"title": "Welcome to yasuri!", "body": "Yasuri is ..."}
151
+
152
+ {
153
+ "text_title": "/html/head/title"}
154
+ }
155
+ # => Welcome to yasuri!
156
+ ```
157
+
158
+
159
+ jsonまたはyaml形式では、子Nodeを持たない場合、`path` を直接値に指定することができます。以下の2つのjsonは同じパースツリーになります。
160
+
161
+ ```json
162
+ {
163
+ "text_name": "/html/body/p"
164
+ }
165
+
166
+ {
167
+ "text_name": {
168
+ "path": "/html/body/p"
169
+ }
170
+ }
171
+ ```
172
+ ### ツリーを実行する
173
+ パースツリーのルートノードで`Node#scrape(uri, opt={})`メソッドをコールします。
174
+
175
+ **例**
97
176
  ```ruby
98
- # トップレベル
99
- Yasuri.<Type>_<Name> <Path> [,<Options>]
177
+ root = Yasuri.links_root '//*[@id="menu"]/ul/li/a' do
178
+ text_title '//*[@id="contents"]/h2'
179
+ text_content '//*[@id="contents"]/p[1]'
180
+ end
100
181
 
101
- # 入れ子になっている場合
102
- Yasuri.<Type>_<Name> <Path> [,<Options>] do
103
- <Type>_<Name> <Path> [,<Options>] do
104
- <Children>
105
- end
106
- end
182
+ result = root.scrape("http://some.scraping.page.tac42.net/", interval_ms: 1000)
107
183
  ```
108
184
 
185
+ + `uri` はスクレイピングする対象ページのURIです。
186
+ + `opt` はオプションをHashで指定します。以下のオプションを利用できます。
187
+
188
+ Yasuriはスクレイピングを行うエージェントとして、内部で`Mechanize`を使用しています。
189
+ このインスタンスを指定したい場合は、`Node#scrape_with_agent(uri, agent, opt={})`をコールします。
190
+
191
+ ```ruby
192
+ require 'logger'
193
+
194
+ agent = Mechanize.new
195
+ agent.log = Logger.new $stderr
196
+ agent.request_headers = {
197
+ # ...
198
+ }
199
+
200
+ result = root.scrape_with_agent(
201
+ "http://some.scraping.page.tac42.net/",
202
+ agent,
203
+ interval_ms: 1000)
204
+ ```
205
+
206
+ ### `opt`
207
+ #### `interval_ms`
208
+ 複数ページにリクエストする際の間隔[ミリ秒]です。
209
+
210
+ 省略した場合はインターバルなしで続けてリクエストしますが、多数のページへのリクエストが予想される場合、対象ホストが高負荷とならないよう、インターバル時間を指定することを強くお勧めします。
211
+
212
+ #### `retry_count`
213
+ ページ取得失敗時のリトライ回数です。省略した場合は5回リトライします。
214
+
215
+ #### `symbolize_names`
216
+ `true`のとき、結果セットのキーをシンボルとして返します。
217
+
218
+ --------------------------
219
+ ## Node
220
+
221
+ Nodeはパースツリーの節または葉となる要素で、`Type`, `Name`, `Path`, `Childlen`, `Options` を持っており、その `Type` に応じてスクレイピングを行います.(ただし、`MapNode` のみ `Path` を持ちません)
222
+
223
+
109
224
  #### Type
110
225
  *Type* は Nodeの振る舞いを示します.Typeには以下のものがあります.
111
226
 
@@ -113,18 +228,21 @@ end
113
228
  - *Struct*
114
229
  - *Links*
115
230
  - *Paginate*
231
+ - *Map*
232
+
233
+ 詳細は各ノードの説明を参照してください。
116
234
 
117
- ### Name
235
+ #### Name
118
236
  *Name* は 解析結果のHashにおけるキーになります.
119
237
 
120
- ### Path
238
+ #### Path
121
239
  *Path* は xpath あるいは css セレクタによって、HTML上の特定のノードを指定します.
122
240
  これは Machinize の `search` で使用されます.
123
241
 
124
- ### Childlen
242
+ #### Childlen
125
243
  入れ子になっているノードの子ノードです.TextNodeはツリーの葉に当たるため、子ノードを持ちません.
126
244
 
127
- ### Options
245
+ #### Options
128
246
  パースのオプションです.オプションはTypeごとに異なります.
129
247
  各ノードに対して、`opt`メソッドをコールすることで、利用可能なオプションを取得できます.
130
248
 
@@ -140,7 +258,7 @@ node.opt #=> {:truncate => /^[^,]+/, :proc => nil}
140
258
  ### 例
141
259
 
142
260
  ```html
143
- <!-- http://yasuri.example.net -->
261
+ <!-- http://yasuri.example.tac42.net -->
144
262
  <html>
145
263
  <head></head>
146
264
  <body>
@@ -151,25 +269,24 @@ node.opt #=> {:truncate => /^[^,]+/, :proc => nil}
151
269
  ```
152
270
 
153
271
  ```ruby
154
- agent = Mechanize.new
155
- page = agent.get("http://yasuri.example.net")
156
-
157
272
  p1 = Yasuri.text_title '/html/body/p[1]'
158
273
  p1t = Yasuri.text_title '/html/body/p[1]', truncate:/^[^,]+/
159
- p2u = Yasuri.text_title '/html/body/p[2]', proc: :upcase
274
+ p2u = Yasuri.text_title '/html/body/p[1]', proc: :upcase
160
275
 
161
- p1.inject(agent, page) #=> { "title" => "Hello,World" }
162
- p1t.inject(agent, page) #=> { "title" => "Hello" }
163
- node.inject(agent, page) #=> { "title" => "HELLO,YASURI" }
276
+ p1.scrape("http://yasuri.example.tac42.net") #=> "Hello,World"
277
+ p1t.scrape("http://yasuri.example.tac42.net") #=> "Hello"
278
+ p2u.scrape("http://yasuri.example.tac42.net") #=> "HELLO,WORLD"
164
279
  ```
165
280
 
281
+ なお、同じページ内の複数の要素を一度にスクレイピングする場合は、`MapNode`を使用します。詳細は、`MapNode`の例を参照してください。
282
+
166
283
  ### オプション
167
284
  ##### `truncate`
168
285
  正規表現にマッチした文字列を取り出します.グループを指定した場合、最初にマッチしたグループだけを返します.
169
286
 
170
287
  ```ruby
171
288
  node = Yasuri.text_example '/html/body/p[1]', truncate:/H(.+)i/
172
- node.inject(agent, index_page)
289
+ node.scrape(uri)
173
290
  #=> { "example" => "ello,Yasur" }
174
291
  ```
175
292
 
@@ -180,7 +297,7 @@ node.inject(agent, index_page)
180
297
 
181
298
  ```ruby
182
299
  node = Yasuri.text_example '/html/body/p[1]', proc: :upcase, truncate:/H(.+)i/
183
- node.inject(agent, index_page)
300
+ node.scrape(uri)
184
301
  #=> { "example" => "ELLO,YASUR" }
185
302
  ```
186
303
 
@@ -195,7 +312,7 @@ Struct Node の `Path` が複数のタグにマッチする場合、配列とし
195
312
  ### 例
196
313
 
197
314
  ```html
198
- <!-- http://yasuri.example.net -->
315
+ <!-- http://yasuri.example.tac42.net -->
199
316
  <html>
200
317
  <head>
201
318
  <title>Books</title>
@@ -236,15 +353,12 @@ Struct Node の `Path` が複数のタグにマッチする場合、配列とし
236
353
  ```
237
354
 
238
355
  ```ruby
239
- agent = Mechanize.new
240
- page = agent.get("http://yasuri.example.net")
241
-
242
356
  node = Yasuri.struct_table '/html/body/table[1]/tr' do
243
357
  text_title './td[1]'
244
358
  text_pub_date './td[2]'
245
- ])
359
+ end
246
360
 
247
- node.inject(agent, page)
361
+ node.scrape("http://yasuri.example.tac42.net")
248
362
  #=> [ { "title" => "The Perfect Insider",
249
363
  # "pub_date" => "1996/4/5" },
250
364
  # { "title" => "Doctors in Isolated Room",
@@ -258,23 +372,19 @@ Struct Node は xpath `'/html/body/table[1]/tr'` によって、最初の `<tabl
258
372
  この場合は、最初の `<table>` は 3つの `<tr>`タグを持っているため、3つのHashを返します.(`<thead><tr>` は `Path` にマッチしないため4つではないことに注意)
259
373
  各HashはTextNodeによってパースされたテキストを含んでいます.
260
374
 
261
-
262
375
  また以下の例のように、Struct Node は TextNode以外のノードを子ノードとすることができます.
263
376
 
264
377
  ### 例
265
378
 
266
379
  ```ruby
267
- agent = Mechanize.new
268
- page = agent.get("http://yasuri.example.net")
269
-
270
380
  node = Yasuri.strucre_tables '/html/body/table' do
271
381
  struct_table './tr' do
272
382
  text_title './td[1]'
273
383
  text_pub_date './td[2]'
274
384
  end
275
- ])
385
+ end
276
386
 
277
- node.inject(agent, page)
387
+ node.scrape("http://yasuri.example.tac42.net")
278
388
 
279
389
  #=> [ { "table" => [ { "title" => "The Perfect Insider",
280
390
  # "pub_date" => "1996/4/5" },
@@ -306,8 +416,8 @@ node.inject(agent, page)
306
416
  Links Node は リンクされた各ページをパースして結果を返します.
307
417
 
308
418
  ### 例
309
- ```
310
- <!-- http://yasuri.example.net -->
419
+ ```html
420
+ <!-- http://yasuri.example.tac42.net -->
311
421
  <html>
312
422
  <head><title>Yasuri Test</title></head>
313
423
  <body>
@@ -319,8 +429,8 @@ Links Node は リンクされた各ページをパースして結果を返し
319
429
  <title>
320
430
  ```
321
431
 
322
- ```
323
- <!-- http://yasuri.example.net/child01.html -->
432
+ ```html
433
+ <!-- http://yasuri.example.tac42.net/child01.html -->
324
434
  <html>
325
435
  <head><title>Child 01 Test</title></head>
326
436
  <body>
@@ -333,8 +443,8 @@ Links Node は リンクされた各ページをパースして結果を返し
333
443
  <title>
334
444
  ```
335
445
 
336
- ```
337
- <!-- http://yasuri.example.net/child02.html -->
446
+ ```html
447
+ <!-- http://yasuri.example.tac42.net/child02.html -->
338
448
  <html>
339
449
  <head><title>Child 02 Test</title></head>
340
450
  <body>
@@ -343,8 +453,8 @@ Links Node は リンクされた各ページをパースして結果を返し
343
453
  <title>
344
454
  ```
345
455
 
346
- ```
347
- <!-- http://yasuri.example.net/child03.html -->
456
+ ```html
457
+ <!-- http://yasuri.example.tac42.net/child03.html -->
348
458
  <html>
349
459
  <head><title>Child 03 Test</title></head>
350
460
  <body>
@@ -356,22 +466,19 @@ Links Node は リンクされた各ページをパースして結果を返し
356
466
  <title>
357
467
  ```
358
468
 
359
- ```
360
- agent = Mechanize.new
361
- page = agent.get("http://yasuri.example.net")
362
-
469
+ ```ruby
363
470
  node = Yasuri.links_title '/html/body/a' do
364
471
  text_content '/html/body/p'
365
472
  end
366
473
 
367
- node.inject(agent, page)
474
+ node.scrape("http://yasuri.example.tac42.net")
368
475
  #=> [ {"content" => "Child 01 page."},
369
476
  {"content" => "Child 02 page."},
370
477
  {"content" => "Child 03 page."}]
371
478
  ```
372
479
 
373
480
  まず、 LinksNode は `Path` にマッチするすべてのリンクを最初のページから探します.
374
- この例では、LinksNodeは `/html/body/a` にマッチするすべてのタグを `http://yasuri.example.net` から探します.
481
+ この例では、LinksNodeは `/html/body/a` にマッチするすべてのタグを `http://yasuri.example.tac42.net` から探します.
375
482
  次に、見つかったタグのhref属性で指定されたページを開きます.(`./child01.html`, `./child02.html`, `./child03.html`)
376
483
 
377
484
  開いた各ページに対して、子ノードによる解析を行います.LinksNodeは 各ページに対するパース結果をHashの配列として返します.
@@ -384,7 +491,7 @@ PaginateNodeは ページネーション(パジネーション, Pagination) で
384
491
  `page02.html` から `page04.html` も同様です.
385
492
 
386
493
  ```html
387
- <!-- http://yasuri.example.net/page01.html -->
494
+ <!-- http://yasuri.example.tac42.net/page01.html -->
388
495
  <html>
389
496
  <head><title>Page01</title></head>
390
497
  <body>
@@ -404,17 +511,14 @@ PaginateNodeは ページネーション(パジネーション, Pagination) で
404
511
  ```
405
512
 
406
513
  ```ruby
407
- agent = Mechanize.new
408
- page = agent.get("http://yasuri.example.net/page01.html")
409
-
410
514
  node = Yasuri.pages_root "/html/body/nav/span/a[@class='next']" , limit:3 do
411
515
  text_content '/html/body/p'
412
516
  end
413
517
 
414
- node.inject(agent, page)
518
+ node.scrape("http://yasuri.example.tac42.net/page01.html")
415
519
  #=> [ {"content" => "Patination01"},
416
- {"content" => "Patination02"},
417
- {"content" => "Patination03"}]
520
+ # {"content" => "Patination02"},
521
+ # {"content" => "Patination03"}]
418
522
  ```
419
523
  PaginateNodeは 次のページ を指すリンクを`Path`として指定する必要があります.
420
524
  この例では、`NextPage` (`/html/body/nav/span/a[@class='next']`)が、次のページを指すリンクに該当します.
@@ -427,7 +531,7 @@ PaginateNodeは 次のページ を指すリンクを`Path`として指定する
427
531
  node = Yasuri.pages_root "/html/body/nav/span/a[@class='next']" , limit:2 do
428
532
  text_content '/html/body/p'
429
533
  end
430
- node.inject(agent, page)
534
+ node.scrape(uri)
431
535
  #=> [ {"content" => "Pagination01"}, {"content" => "Pagination02"}]
432
536
  ```
433
537
  この場合、PaginateNode は最大2つまでのページを開いてパースします.ページネーションは4つのページを持っているようですが、`limit:2`が指定されているため、結果の配列には2つの結果のみが含まれています.
@@ -436,33 +540,177 @@ node.inject(agent, page)
436
540
  取得した各ページの結果を展開します.
437
541
 
438
542
  ```ruby
439
- agent = Mechanize.new
440
- page = agent.get("http://yasuri.example.net/page01.html")
441
-
442
543
  node = Yasuri.pages_root "/html/body/nav/span/a[@class='next']" , flatten:true do
443
544
  text_title '/html/head/title'
444
545
  text_content '/html/body/p'
445
546
  end
446
- node.inject(agent, page)
547
+ node.scrape("http://yasuri.example.tac42.net/page01.html")
447
548
 
448
549
  #=> [ {"title" => "Page01",
449
- "content" => "Patination01"},
450
- {"title" => "Page01",
451
- "content" => "Patination02"},
452
- {"title" => "Page01",
453
- "content" => "Patination03"}]
550
+ # "content" => "Patination01"},
551
+ # {"title" => "Page01",
552
+ # "content" => "Patination02"},
553
+ # {"title" => "Page01",
554
+ # "content" => "Patination03"}]
454
555
 
455
556
 
456
557
  node = Yasuri.pages_root "/html/body/nav/span/a[@class='next']" , flatten:true do
457
558
  text_title '/html/head/title'
458
559
  text_content '/html/body/p'
459
560
  end
460
- node.inject(agent, page)
561
+ node.scrape("http://yasuri.example.tac42.net/page01.html")
461
562
 
462
563
  #=> [ "Page01",
463
- "Patination01",
464
- "Page02",
465
- "Patination02",
466
- "Page03",
467
- "Patination03"]
564
+ # "Patination01",
565
+ # "Page02",
566
+ # "Patination02",
567
+ # "Page03",
568
+ # "Patination03"]
569
+ ```
570
+
571
+ ## Map Node
572
+ *MapNode* はスクレイピングした結果をまとめるノードです.このノードはパースツリーにおいて常に節です.
573
+
574
+ ### 例
575
+
576
+ ```html
577
+ <!-- http://yasuri.example.tac42.net -->
578
+ <html>
579
+ <head><title>Yasuri Example</title></head>
580
+ <body>
581
+ <p>Hello,World</p>
582
+ <p>Hello,Yasuri</p>
583
+ </body>
584
+ </html>
585
+ ```
586
+
587
+ ```ruby
588
+ tree = Yasuri.map_root do
589
+ text_title '/html/head/title'
590
+ text_body_p '/html/body/p[1]'
591
+ end
592
+
593
+ tree.scrape("http://yasuri.example.tac42.net") #=> { "title" => "Yasuri Example", "body_p" => "Hello,World" }
594
+
595
+
596
+ tree = Yasuri.map_root do
597
+ map_group1 { text_child01 '/html/body/a[1]' }
598
+ map_group2 do
599
+ text_child01 '/html/body/a[1]'
600
+ text_child03 '/html/body/a[3]'
601
+ end
602
+ end
603
+
604
+ tree.scrape("http://yasuri.example.tac42.net") #=> {
605
+ # "group1" => {
606
+ # "child01" => "child01"
607
+ # },
608
+ # "group2" => {
609
+ # "child01" => "child01",
610
+ # "child03" => "child03"
611
+ # }
612
+ # }
468
613
  ```
614
+
615
+ ### オプション
616
+ なし
617
+
618
+
619
+ -------------------------
620
+ ## 使い方
621
+
622
+ ### ライブラリとして使う
623
+ ライブラリとして使用する場合は、DSL, json, yaml の形式でツリーを定義できます。
624
+
625
+ ```ruby
626
+ require 'yasuri'
627
+
628
+ # 1. パースツリーを作る
629
+ # DSLで定義する
630
+ tree = Yasuri.links_title '/html/body/a' do
631
+ text_name '/html/body/p'
632
+ end
633
+
634
+ # jsonで定義する場合
635
+ src = <<-EOJSON
636
+ {
637
+ links_title": {
638
+ "path": "/html/body/a",
639
+ "text_name": "/html/body/p"
640
+ }
641
+ }
642
+ EOJSON
643
+ tree = Yasuri.json2tree(src)
644
+
645
+
646
+ # yamlで定義する場合
647
+ src = <<-EOYAML
648
+ links_title:
649
+ path: "/html/body/a"
650
+ text_name: "/html/body/p"
651
+ EOYAML
652
+ tree = Yasuri.yaml2tree(src)
653
+
654
+ # 2. URLを与えてパースを開始する
655
+ tree.inject(uri)
656
+ ```
657
+
658
+ ### CLIツールとして使う
659
+
660
+ **ヘルプ表示**
661
+ ```sh
662
+ $ yasuri help scrape
663
+ Usage:
664
+ yasuri scrape <URI> [[--file <TREE_FILE>] or [--json <JSON>]]
665
+
666
+ Options:
667
+ f, [--file=FILE] # path to file that written yasuri tree as json or yaml
668
+ j, [--json=JSON] # yasuri tree format json string
669
+ i, [--interval=N] # interval each request [ms]
670
+
671
+ Getting from <URI> and scrape it. with <JSON> or json/yml from <TREE_FILE>. They should be Yasuri's format json or yaml string.
672
+ ```
673
+
674
+ CLIツールでは以下のどちらかの方法でパースツリーを指定します。
675
+ + `--file`, `-f` : ファイルに出力されたjson形式またはyaml形式のパースツリーを読み込む
676
+ + `--json`, `-j` : パースツリーを文字列として直接指定する
677
+
678
+
679
+ **パースツリーをファイルで指定する例**
680
+ ```sh
681
+ % cat sample.yml
682
+ text_title: "/html/head/title"
683
+ text_desc: "//*[@id=\"intro\"]/p"
684
+
685
+ % yasuri scrape "https://www.ruby-lang.org/en/" --file sample.yml
686
+ {"title":"Ruby Programming Language","desc":"\n A dynamic, open source programming language with a focus on\n simplicity and productivity. It has an elegant syntax that is\n natural to read and easy to write.\n "}
687
+
688
+ % cat sample.json
689
+ {
690
+ "text_title": "/html/head/title",
691
+ "text_desc": "//*[@id=\"intro\"]/p"
692
+ }
693
+
694
+ % yasuri scrape "https://www.ruby-lang.org/en/" --file sample.json
695
+ {"title":"Ruby Programming Language","desc":"\n A dynamic, open source programming language with a focus on\n simplicity and productivity. It has an elegant syntax that is\n natural to read and easy to write.\n "}
696
+ ```
697
+
698
+ ファイルがjsonまたはyamlのどちらで記載されているかについては自動判別されます。
699
+
700
+ **パースツリーをjsonで直接指定する例**
701
+ ```sh
702
+ $ yasuri scrape "https://www.ruby-lang.org/en/" -j '
703
+ {
704
+ "text_title": "/html/head/title",
705
+ "text_desc": "//*[@id=\"intro\"]/p"
706
+ }'
707
+
708
+ {"title":"Ruby Programming Language","desc":"\n A dynamic, open source programming language with a focus on\n simplicity and productivity. It has an elegant syntax that is\n natural to read and easy to write.\n "}
709
+ ```
710
+
711
+ #### その他のオプション
712
+ + `--interval`, `-i` : 複数ページにリクエストする際の間隔[ミリ秒]です。
713
+ **例: 1秒間隔でリクエストする**
714
+ ```sh
715
+ $ yasuri scrape "https://www.ruby-lang.org/en/" --file sample.yml --interval 1000
716
+ ```