yasuri 2.0.13 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5788b7152b19e7be6d0da7ae376784eeedc52b5f
4
- data.tar.gz: 40b187d47b1bf468c8d37fe688906a0c7c3f2858
2
+ SHA256:
3
+ metadata.gz: 0ec1d3a8cc766976c1d1329448a52df1696ccb50c41bf7c714b28cd265470d54
4
+ data.tar.gz: a87a1cd109c0e3dd8d820d3f35e34ef3096ef493d53d9c6bab0a79f1ccd7df9a
5
5
  SHA512:
6
- metadata.gz: a338eecbdb1412b8da0f7201fc333b30aaab59e9065f6b92eef1c0d542705fa8a536907e9084809f2a7a2156d39462e4f397ac0e4e9352b94d9cef9617bab6b2
7
- data.tar.gz: 99a7ecad43ba424566ae4a3635d03dab35f9224de800a3f865c01f8075c91ba5db1451f8481b35e397ab4877c790da3ecea6fce646e2981190a86835f0abc3ca
6
+ metadata.gz: 5f7787bfdc549e70b9e5a5ae45f84f3a0316142deea3c75f17d9781c8140e597d41ccbfd1679e8186799f76e771a24a1d6129402c818f5d6e3ea0b94128a2185
7
+ data.tar.gz: 61e430d7dae6fda7c28a9456f1751206cfcd01321ff71feeeba7447606a1eb10d7270dda5c8947a4cc8edb3dae60d6d57fc79f127e61920249fcab0ca47f4e37
@@ -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.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/.rubocop.yml ADDED
@@ -0,0 +1,49 @@
1
+
2
+ # inherit_from: .rubocop_todo.yml
3
+
4
+ inherit_mode:
5
+ merge:
6
+ - Exclude
7
+
8
+ require:
9
+ - rubocop-performance
10
+ - rubocop-rspec
11
+ - rubocop-rubycw
12
+
13
+ AllCops:
14
+ DisabledByDefault: true
15
+ DisplayCopNames: true
16
+ Exclude:
17
+ - 'gems/**/*'
18
+ - 'pkg/**/*'
19
+ - 'coverage/**/*'
20
+ - 'exe/**/*'
21
+
22
+ NewCops: enable
23
+
24
+ Bundler:
25
+ Enabled: true
26
+
27
+ Gemspec:
28
+ Enabled: true
29
+
30
+ Lint:
31
+ Enabled: true
32
+
33
+ Performance:
34
+ Enabled: true
35
+
36
+ Rubycw:
37
+ Enabled: true
38
+
39
+ Security:
40
+ Enabled: true
41
+
42
+ Style/HashSyntax:
43
+ EnforcedStyle: ruby19
44
+ Style/HashEachMethods:
45
+ Enabled: true
46
+ Style/HashTransformKeys:
47
+ Enabled: true
48
+ Style/HashTransformValues:
49
+ Enabled: true
data/.rubocop_todo.yml ADDED
File without changes
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.3.2
1
+ 3.0.0
data/README.md CHANGED
@@ -1,8 +1,12 @@
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 a library for declarative web scraping and a command line tool for scraping with it.
6
+ It performs scraping by simply describing the expected result in a simple declarative notation.
4
7
 
5
- Yasuri can reduce frequently processes in Scraping.
8
+ Yasuri makes it easy to write common scraping operations.
9
+ For example, the following processes can be easily implemented.
6
10
 
7
11
  For example,
8
12
 
@@ -31,7 +35,10 @@ or
31
35
 
32
36
  ```ruby
33
37
  # for Ruby 1.9.3 or lower
34
- gem 'yasuri', '~> 1.9'
38
+ gem 'yasuri', '~> 2.0', '>= 2.0.13'
39
+
40
+ # for Ruby 3.0.0 or lower
41
+ gem 'yasuri', '~> 3.1'
35
42
  ```
36
43
 
37
44
 
@@ -44,6 +51,7 @@ Or install it yourself as:
44
51
  $ gem install yasuri
45
52
 
46
53
  ## Usage
54
+ ### Use as library
47
55
 
48
56
  ```ruby
49
57
  # Node tree constructing by DSL
@@ -55,46 +63,89 @@ root = Yasuri.links_root '//*[@id="menu"]/ul/li/a' do
55
63
 
56
64
  # Node tree constructing by YAML
57
65
  src = <<-EOYAML
58
- root:
59
- node: links
66
+ links_root:
60
67
  path: "//*[@id='menu']/ul/li/a"
61
- children:
62
- - title:
63
- node: text
64
- path: "//*[@id='contents']/h2"
65
- - content:
66
- node: text
67
- path: "//*[@id='contents']/p[1]"
68
+ text_title: "//*[@id='contents']/h2"
69
+ text_content: "//*[@id='contents']/p[1]"
68
70
  EOYAML
69
71
  root = Yasuri.yaml2tree(src)
70
72
 
71
73
 
72
74
  # Node tree constructing by JSON
73
75
  src = <<-EOJSON
74
- { "node" : "links",
75
- "name" : "root",
76
- "path" : "//*[@id='menu']/ul/li/a",
77
- "children" : [
78
- { "node" : "text",
79
- "name" : "title",
80
- "path" : "//*[@id='contents']/h2"
81
- },
82
- { "node" : "text",
83
- "name" : "content",
84
- "path" : "//*[@id='contents']/p[1]"
85
- }
86
- ]
87
- }
76
+ {
77
+ "links_root": {
78
+ "path": "//*[@id='menu']/ul/li/a",
79
+ "text_title": "//*[@id='contents']/h2",
80
+ "text_content": "//*[@id='contents']/p[1]"
81
+ }
82
+ }
88
83
  EOJSON
89
84
  root = Yasuri.json2tree(src)
90
85
 
91
- agent = Mechanize.new
92
- root_page = agent.get("http://some.scraping.page.net/")
86
+ # Execution and getting scraped result
87
+ result = root.scrape("http://some.scraping.page.tac42.net/")
88
+ # => [
89
+ # {"title" => "PageTitle 01", "content" => "Page Contents 01" },
90
+ # {"title" => "PageTitle 02", "content" => "Page Contents 02" },
91
+ # ...
92
+ # {"title" => "PageTitle N", "content" => "Page Contents N" }
93
+ # ]
94
+ ```
95
+
96
+ ### Use as CLI
97
+
98
+ ```sh
99
+ # After gem installation..
100
+ $ yasuri help scrape
101
+ Usage:
102
+ yasuri scrape <URI> [[--file <TREE_FILE>] or [--json <JSON>]]
93
103
 
94
- result = root.inject(agent, root_page)
95
- # => [ {"title" => "PageTitle", "content" => "Page Contents" }, ... ]
104
+ Options:
105
+ f, [--file=FILE] # path to file that written yasuri tree as json or yaml
106
+ j, [--json=JSON] # yasuri tree format json string
107
+ i, [--interval=N] # interval each request [ms]
108
+
109
+ Getting from <URI> and scrape it. with <JSON> or json/yml from <TREE_FILE>. They should be Yasuri's format json or yaml string.
96
110
  ```
97
111
 
112
+ Example
113
+ ```sh
114
+ $ yasuri scrape "https://www.ruby-lang.org/en/" -j '
115
+ {
116
+ "text_title": "/html/head/title",
117
+ "text_desc": "//*[@id=\"intro\"]/p"
118
+ }'
119
+
120
+ {"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 "}
121
+ ```
122
+
123
+ ## Dev
124
+ ```sh
125
+ $ gem install bundler
126
+ $ bundle install
127
+ ```
128
+ ### Test
129
+ ```sh
130
+ $ rake
131
+ # or
132
+ $ rspec spec/*spec.rb
133
+ ```
134
+
135
+ ### Test gem in local
136
+ ```sh
137
+ $ gem build yasuri.gemspec
138
+ $ gem install yasuri-*.gem
139
+ ```
140
+ ### Release RubyGems
141
+ ```sh
142
+ # Only first time
143
+ $ curl -u <user_name> https://rubygems.org/api/v1/api_key.yaml > ~/.gem/credentials
144
+ $ chmod 0600 ~/.gem/credentials
145
+
146
+ $ nano lib/yasuri/version.rb # edit gem version
147
+ $ rake release
148
+ ```
98
149
 
99
150
  ## Contributing
100
151
 
data/Rakefile CHANGED
@@ -3,5 +3,5 @@ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
7
7
 
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,96 +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
61
-
62
- # 2. Mechanize の agent と対象のページを与えてパースを開始する
63
- agent = Mechanize.new
64
- page = agent.get(uri)
84
+ パースツリーは以下のフォーマットで定義されます.
65
85
 
86
+ ```ruby
87
+ # 1ノードからなる単純なツリー
88
+ Yasuri.<Type>_<Name> <Path> [,<Options>]
66
89
 
67
- tree.inject(agent, page)
90
+ # 入れ子になっているツリー
91
+ Yasuri.<Type>_<Name> <Path> [,<Options>] do
92
+ <Type>_<Name> <Path> [,<Options>] do
93
+ <Type>_<Name> <Path> [,<Options>]
94
+ ...
95
+ end
96
+ end
68
97
  ```
69
98
 
70
- ツリーは、json,yaml,またはDSLで定義することができます.上の例ではDSLで定義しています.
71
- 以下は、jsonで上記と等価な解析ツリーを定義した例です.
99
+ **例**
72
100
 
73
101
  ```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)
102
+ # 1ノードからなる単純なツリー
103
+ Yasuri.text_title '/html/head/title', truncate:/^[^,]+/
104
+
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
88
112
  ```
89
113
 
114
+
115
+ パースツリーはRubyのDSL、JSON、YAMLのいずれかで定義することができます。
116
+ 以下は、上記と同じパースツリーをそれぞれの記法で定義した例です。
117
+
118
+ **Ruby DSLで定義する場合**
90
119
  ```ruby
91
- # yaml で構成する場合
92
- src = <<-EOYAML
93
- title:
94
- node: links
120
+ Yasuri.links_title '/html/body/a' do
121
+ text_name '/html/body/p'
122
+ end
123
+ ```
124
+
125
+ **JSONで定義する場合**
126
+ ```json
127
+ {
128
+ links_title": {
129
+ "path": "/html/body/a",
130
+ "text_name": "/html/body/p"
131
+ }
132
+ }
133
+ ```
134
+
135
+ **YAMLで定義する場合**
136
+ ```yaml
137
+ links_title:
95
138
  path: "/html/body/a"
96
- children:
97
- - name:
98
- node: text
99
- path: "/html/body/p"
100
- EOYAML
101
- tree = Yasuri.yaml2tree(src)
139
+ text_name: "/html/body/p"
140
+ ```
141
+
142
+ **パースツリーの特殊なケース**
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!
102
156
  ```
103
157
 
104
- ### Node
105
- ツリーは入れ子になった *Node* で構成されます.
106
- Node は `Type`, `Name`, `Path`, `Childlen`, `Options` を持っています.
107
158
 
108
- Nodeは以下のフォーマットで定義されます.
159
+ jsonまたはyaml形式では、子Nodeを持たない場合、`path` を直接値に指定することができます。以下の2つのjsonは同じパースツリーになります。
160
+
161
+ ```json
162
+ {
163
+ "text_name": "/html/body/p"
164
+ }
109
165
 
166
+ {
167
+ "text_name": {
168
+ "path": "/html/body/p"
169
+ }
170
+ }
171
+ ```
172
+ ### ツリーを実行する
173
+ パースツリーのルートノードで`Node#scrape(uri, opt={})`メソッドをコールします。
174
+
175
+ **例**
110
176
  ```ruby
111
- # トップレベル
112
- 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
113
181
 
114
- # 入れ子になっている場合
115
- Yasuri.<Type>_<Name> <Path> [,<Options>] do
116
- <Type>_<Name> <Path> [,<Options>] do
117
- <Children>
118
- end
119
- end
182
+ result = root.scrape("http://some.scraping.page.tac42.net/", interval_ms: 1000)
120
183
  ```
121
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
+
122
224
  #### Type
123
225
  *Type* は Nodeの振る舞いを示します.Typeには以下のものがあります.
124
226
 
@@ -126,18 +228,21 @@ end
126
228
  - *Struct*
127
229
  - *Links*
128
230
  - *Paginate*
231
+ - *Map*
129
232
 
130
- ### Name
233
+ 詳細は各ノードの説明を参照してください。
234
+
235
+ #### Name
131
236
  *Name* は 解析結果のHashにおけるキーになります.
132
237
 
133
- ### Path
238
+ #### Path
134
239
  *Path* は xpath あるいは css セレクタによって、HTML上の特定のノードを指定します.
135
240
  これは Machinize の `search` で使用されます.
136
241
 
137
- ### Childlen
242
+ #### Childlen
138
243
  入れ子になっているノードの子ノードです.TextNodeはツリーの葉に当たるため、子ノードを持ちません.
139
244
 
140
- ### Options
245
+ #### Options
141
246
  パースのオプションです.オプションはTypeごとに異なります.
142
247
  各ノードに対して、`opt`メソッドをコールすることで、利用可能なオプションを取得できます.
143
248
 
@@ -153,7 +258,7 @@ node.opt #=> {:truncate => /^[^,]+/, :proc => nil}
153
258
  ### 例
154
259
 
155
260
  ```html
156
- <!-- http://yasuri.example.net -->
261
+ <!-- http://yasuri.example.tac42.net -->
157
262
  <html>
158
263
  <head></head>
159
264
  <body>
@@ -164,25 +269,24 @@ node.opt #=> {:truncate => /^[^,]+/, :proc => nil}
164
269
  ```
165
270
 
166
271
  ```ruby
167
- agent = Mechanize.new
168
- page = agent.get("http://yasuri.example.net")
169
-
170
272
  p1 = Yasuri.text_title '/html/body/p[1]'
171
273
  p1t = Yasuri.text_title '/html/body/p[1]', truncate:/^[^,]+/
172
- p2u = Yasuri.text_title '/html/body/p[2]', proc: :upcase
274
+ p2u = Yasuri.text_title '/html/body/p[1]', proc: :upcase
173
275
 
174
- p1.inject(agent, page) #=> { "title" => "Hello,World" }
175
- p1t.inject(agent, page) #=> { "title" => "Hello" }
176
- 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"
177
279
  ```
178
280
 
281
+ なお、同じページ内の複数の要素を一度にスクレイピングする場合は、`MapNode`を使用します。詳細は、`MapNode`の例を参照してください。
282
+
179
283
  ### オプション
180
284
  ##### `truncate`
181
285
  正規表現にマッチした文字列を取り出します.グループを指定した場合、最初にマッチしたグループだけを返します.
182
286
 
183
287
  ```ruby
184
288
  node = Yasuri.text_example '/html/body/p[1]', truncate:/H(.+)i/
185
- node.inject(agent, index_page)
289
+ node.scrape(uri)
186
290
  #=> { "example" => "ello,Yasur" }
187
291
  ```
188
292
 
@@ -193,7 +297,7 @@ node.inject(agent, index_page)
193
297
 
194
298
  ```ruby
195
299
  node = Yasuri.text_example '/html/body/p[1]', proc: :upcase, truncate:/H(.+)i/
196
- node.inject(agent, index_page)
300
+ node.scrape(uri)
197
301
  #=> { "example" => "ELLO,YASUR" }
198
302
  ```
199
303
 
@@ -208,7 +312,7 @@ Struct Node の `Path` が複数のタグにマッチする場合、配列とし
208
312
  ### 例
209
313
 
210
314
  ```html
211
- <!-- http://yasuri.example.net -->
315
+ <!-- http://yasuri.example.tac42.net -->
212
316
  <html>
213
317
  <head>
214
318
  <title>Books</title>
@@ -249,15 +353,12 @@ Struct Node の `Path` が複数のタグにマッチする場合、配列とし
249
353
  ```
250
354
 
251
355
  ```ruby
252
- agent = Mechanize.new
253
- page = agent.get("http://yasuri.example.net")
254
-
255
356
  node = Yasuri.struct_table '/html/body/table[1]/tr' do
256
357
  text_title './td[1]'
257
358
  text_pub_date './td[2]'
258
- ])
359
+ end
259
360
 
260
- node.inject(agent, page)
361
+ node.scrape("http://yasuri.example.tac42.net")
261
362
  #=> [ { "title" => "The Perfect Insider",
262
363
  # "pub_date" => "1996/4/5" },
263
364
  # { "title" => "Doctors in Isolated Room",
@@ -271,23 +372,19 @@ Struct Node は xpath `'/html/body/table[1]/tr'` によって、最初の `<tabl
271
372
  この場合は、最初の `<table>` は 3つの `<tr>`タグを持っているため、3つのHashを返します.(`<thead><tr>` は `Path` にマッチしないため4つではないことに注意)
272
373
  各HashはTextNodeによってパースされたテキストを含んでいます.
273
374
 
274
-
275
375
  また以下の例のように、Struct Node は TextNode以外のノードを子ノードとすることができます.
276
376
 
277
377
  ### 例
278
378
 
279
379
  ```ruby
280
- agent = Mechanize.new
281
- page = agent.get("http://yasuri.example.net")
282
-
283
380
  node = Yasuri.strucre_tables '/html/body/table' do
284
381
  struct_table './tr' do
285
382
  text_title './td[1]'
286
383
  text_pub_date './td[2]'
287
384
  end
288
- ])
385
+ end
289
386
 
290
- node.inject(agent, page)
387
+ node.scrape("http://yasuri.example.tac42.net")
291
388
 
292
389
  #=> [ { "table" => [ { "title" => "The Perfect Insider",
293
390
  # "pub_date" => "1996/4/5" },
@@ -319,8 +416,8 @@ node.inject(agent, page)
319
416
  Links Node は リンクされた各ページをパースして結果を返します.
320
417
 
321
418
  ### 例
322
- ```
323
- <!-- http://yasuri.example.net -->
419
+ ```html
420
+ <!-- http://yasuri.example.tac42.net -->
324
421
  <html>
325
422
  <head><title>Yasuri Test</title></head>
326
423
  <body>
@@ -332,8 +429,8 @@ Links Node は リンクされた各ページをパースして結果を返し
332
429
  <title>
333
430
  ```
334
431
 
335
- ```
336
- <!-- http://yasuri.example.net/child01.html -->
432
+ ```html
433
+ <!-- http://yasuri.example.tac42.net/child01.html -->
337
434
  <html>
338
435
  <head><title>Child 01 Test</title></head>
339
436
  <body>
@@ -346,8 +443,8 @@ Links Node は リンクされた各ページをパースして結果を返し
346
443
  <title>
347
444
  ```
348
445
 
349
- ```
350
- <!-- http://yasuri.example.net/child02.html -->
446
+ ```html
447
+ <!-- http://yasuri.example.tac42.net/child02.html -->
351
448
  <html>
352
449
  <head><title>Child 02 Test</title></head>
353
450
  <body>
@@ -356,8 +453,8 @@ Links Node は リンクされた各ページをパースして結果を返し
356
453
  <title>
357
454
  ```
358
455
 
359
- ```
360
- <!-- http://yasuri.example.net/child03.html -->
456
+ ```html
457
+ <!-- http://yasuri.example.tac42.net/child03.html -->
361
458
  <html>
362
459
  <head><title>Child 03 Test</title></head>
363
460
  <body>
@@ -369,22 +466,19 @@ Links Node は リンクされた各ページをパースして結果を返し
369
466
  <title>
370
467
  ```
371
468
 
372
- ```
373
- agent = Mechanize.new
374
- page = agent.get("http://yasuri.example.net")
375
-
469
+ ```ruby
376
470
  node = Yasuri.links_title '/html/body/a' do
377
471
  text_content '/html/body/p'
378
472
  end
379
473
 
380
- node.inject(agent, page)
474
+ node.scrape("http://yasuri.example.tac42.net")
381
475
  #=> [ {"content" => "Child 01 page."},
382
476
  {"content" => "Child 02 page."},
383
477
  {"content" => "Child 03 page."}]
384
478
  ```
385
479
 
386
480
  まず、 LinksNode は `Path` にマッチするすべてのリンクを最初のページから探します.
387
- この例では、LinksNodeは `/html/body/a` にマッチするすべてのタグを `http://yasuri.example.net` から探します.
481
+ この例では、LinksNodeは `/html/body/a` にマッチするすべてのタグを `http://yasuri.example.tac42.net` から探します.
388
482
  次に、見つかったタグのhref属性で指定されたページを開きます.(`./child01.html`, `./child02.html`, `./child03.html`)
389
483
 
390
484
  開いた各ページに対して、子ノードによる解析を行います.LinksNodeは 各ページに対するパース結果をHashの配列として返します.
@@ -397,7 +491,7 @@ PaginateNodeは ページネーション(パジネーション, Pagination) で
397
491
  `page02.html` から `page04.html` も同様です.
398
492
 
399
493
  ```html
400
- <!-- http://yasuri.example.net/page01.html -->
494
+ <!-- http://yasuri.example.tac42.net/page01.html -->
401
495
  <html>
402
496
  <head><title>Page01</title></head>
403
497
  <body>
@@ -417,17 +511,14 @@ PaginateNodeは ページネーション(パジネーション, Pagination) で
417
511
  ```
418
512
 
419
513
  ```ruby
420
- agent = Mechanize.new
421
- page = agent.get("http://yasuri.example.net/page01.html")
422
-
423
514
  node = Yasuri.pages_root "/html/body/nav/span/a[@class='next']" , limit:3 do
424
515
  text_content '/html/body/p'
425
516
  end
426
517
 
427
- node.inject(agent, page)
518
+ node.scrape("http://yasuri.example.tac42.net/page01.html")
428
519
  #=> [ {"content" => "Patination01"},
429
- {"content" => "Patination02"},
430
- {"content" => "Patination03"}]
520
+ # {"content" => "Patination02"},
521
+ # {"content" => "Patination03"}]
431
522
  ```
432
523
  PaginateNodeは 次のページ を指すリンクを`Path`として指定する必要があります.
433
524
  この例では、`NextPage` (`/html/body/nav/span/a[@class='next']`)が、次のページを指すリンクに該当します.
@@ -440,7 +531,7 @@ PaginateNodeは 次のページ を指すリンクを`Path`として指定する
440
531
  node = Yasuri.pages_root "/html/body/nav/span/a[@class='next']" , limit:2 do
441
532
  text_content '/html/body/p'
442
533
  end
443
- node.inject(agent, page)
534
+ node.scrape(uri)
444
535
  #=> [ {"content" => "Pagination01"}, {"content" => "Pagination02"}]
445
536
  ```
446
537
  この場合、PaginateNode は最大2つまでのページを開いてパースします.ページネーションは4つのページを持っているようですが、`limit:2`が指定されているため、結果の配列には2つの結果のみが含まれています.
@@ -449,33 +540,177 @@ node.inject(agent, page)
449
540
  取得した各ページの結果を展開します.
450
541
 
451
542
  ```ruby
452
- agent = Mechanize.new
453
- page = agent.get("http://yasuri.example.net/page01.html")
454
-
455
543
  node = Yasuri.pages_root "/html/body/nav/span/a[@class='next']" , flatten:true do
456
544
  text_title '/html/head/title'
457
545
  text_content '/html/body/p'
458
546
  end
459
- node.inject(agent, page)
547
+ node.scrape("http://yasuri.example.tac42.net/page01.html")
460
548
 
461
549
  #=> [ {"title" => "Page01",
462
- "content" => "Patination01"},
463
- {"title" => "Page01",
464
- "content" => "Patination02"},
465
- {"title" => "Page01",
466
- "content" => "Patination03"}]
550
+ # "content" => "Patination01"},
551
+ # {"title" => "Page01",
552
+ # "content" => "Patination02"},
553
+ # {"title" => "Page01",
554
+ # "content" => "Patination03"}]
467
555
 
468
556
 
469
557
  node = Yasuri.pages_root "/html/body/nav/span/a[@class='next']" , flatten:true do
470
558
  text_title '/html/head/title'
471
559
  text_content '/html/body/p'
472
560
  end
473
- node.inject(agent, page)
561
+ node.scrape("http://yasuri.example.tac42.net/page01.html")
474
562
 
475
563
  #=> [ "Page01",
476
- "Patination01",
477
- "Page02",
478
- "Patination02",
479
- "Page03",
480
- "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
+ # }
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 "}
481
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
+ ```