yasuri 2.0.12 → 3.3.0
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 +5 -5
- data/.github/workflows/ruby.yml +35 -0
- data/.gitignore +1 -2
- data/.ruby-version +1 -0
- data/.travis.yml +1 -3
- data/README.md +87 -21
- data/USAGE.ja.md +368 -120
- data/USAGE.md +375 -125
- data/examples/example.rb +79 -0
- data/examples/github.yml +15 -0
- data/examples/sample.json +4 -0
- data/examples/sample.yml +11 -0
- data/exe/yasuri +5 -0
- data/lib/yasuri.rb +1 -0
- data/lib/yasuri/version.rb +1 -1
- data/lib/yasuri/yasuri.rb +86 -41
- data/lib/yasuri/yasuri_cli.rb +64 -0
- data/lib/yasuri/yasuri_links_node.rb +11 -5
- data/lib/yasuri/yasuri_map_node.rb +40 -0
- data/lib/yasuri/yasuri_node.rb +37 -2
- data/lib/yasuri/yasuri_node_generator.rb +16 -11
- data/lib/yasuri/yasuri_paginate_node.rb +10 -4
- data/lib/yasuri/yasuri_struct_node.rb +5 -1
- data/lib/yasuri/yasuri_text_node.rb +9 -2
- data/spec/cli_resources/tree.json +8 -0
- data/spec/cli_resources/tree.yml +5 -0
- data/spec/cli_resources/tree_wrong.json +9 -0
- data/spec/cli_resources/tree_wrong.yml +6 -0
- data/spec/spec_helper.rb +4 -9
- data/spec/yasuri_cli_spec.rb +96 -0
- data/spec/yasuri_links_node_spec.rb +34 -12
- data/spec/yasuri_map_spec.rb +75 -0
- data/spec/yasuri_paginate_node_spec.rb +22 -10
- data/spec/yasuri_spec.rb +244 -94
- data/spec/yasuri_struct_node_spec.rb +13 -17
- data/spec/yasuri_text_node_spec.rb +11 -12
- data/yasuri.gemspec +5 -3
- metadata +52 -18
- data/app.rb +0 -52
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a7bf438a08fc83fec7e78cb5543577c98f6cc98b4f5fae7b0dd969f2049c0531
|
4
|
+
data.tar.gz: e399c6b57589b7d8ba2e8eff7a1d204fa7f8e676f82f631057e19a9377333060
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.0.0
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
# Yasuri
|
1
|
+
# Yasuri
|
2
|
+
[](https://github.com/tac0x2a/yasuri/actions/workflows/ruby.yml)
|
3
|
+
[](https://coveralls.io/r/tac0x2a/yasuri?branch=master) [](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', '~>
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
75
|
-
|
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
|
-
|
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 (鑢)
|
4
|
+
Yasuri (鑢) はWebスクレイピングを宣言的に行うためのライブラリと、それを用いたスクレイピングのコマンドラインツールです。
|
5
|
+
|
6
|
+
簡単な宣言的記法で期待結果を記述するだけでスクレイピングした結果を得られます。
|
5
7
|
|
6
8
|
Yasuriは、スクレイピングにおける、よくある処理を簡単に記述することができます.
|
7
|
-
|
9
|
+
例えば、以下のような処理を簡単に実現することができます.
|
8
10
|
|
9
|
-
+ ページ内の複数のリンクを開いて、各ページをスクレイピングした結果をHashで取得する
|
10
11
|
+ ページ内の複数のテキストをスクレイピングし、名前をつけてHashにする
|
12
|
+
+ ページ内の複数のリンクを開いて、各ページをスクレイピングした結果をHashで取得する
|
11
13
|
+ ページ内に繰り返し出現するテーブルをそれぞれスクレイピングして、配列として取得する
|
12
|
-
+
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
#
|
37
|
-
# {"title" => "
|
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
|
-
|
47
|
-
|
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
|
-
|
53
|
-
|
54
|
-
require 'yasuri'
|
80
|
+
パースツリーとは、スクレイピングする要素と出力構造を宣言的に定義するための木構造データです。
|
81
|
+
パースツリーは入れ子になった Node で構成されます.Node は `Type`, `Name`, `Path`, `Childlen`, `Options` 属性を持っており、その `Type` に応じたスクレイピング処理を行います.(ただし、`MapNode` のみ `Path` を持ちません)
|
55
82
|
|
56
83
|
|
57
|
-
|
58
|
-
tree = Yasuri.links_title '/html/body/a' do
|
59
|
-
text_name '/html/body/p'
|
60
|
-
end
|
84
|
+
パースツリーは以下のフォーマットで定義されます.
|
61
85
|
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
135
|
+
**YAMLで定義する場合**
|
136
|
+
```yaml
|
137
|
+
links_title:
|
138
|
+
path: "/html/body/a"
|
139
|
+
text_name: "/html/body/p"
|
140
|
+
```
|
94
141
|
|
95
|
-
|
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
|
-
|
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
|
-
|
235
|
+
#### Name
|
118
236
|
*Name* は 解析結果のHashにおけるキーになります.
|
119
237
|
|
120
|
-
|
238
|
+
#### Path
|
121
239
|
*Path* は xpath あるいは css セレクタによって、HTML上の特定のノードを指定します.
|
122
240
|
これは Machinize の `search` で使用されます.
|
123
241
|
|
124
|
-
|
242
|
+
#### Childlen
|
125
243
|
入れ子になっているノードの子ノードです.TextNodeはツリーの葉に当たるため、子ノードを持ちません.
|
126
244
|
|
127
|
-
|
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[
|
274
|
+
p2u = Yasuri.text_title '/html/body/p[1]', proc: :upcase
|
160
275
|
|
161
|
-
p1.
|
162
|
-
p1t.
|
163
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
518
|
+
node.scrape("http://yasuri.example.tac42.net/page01.html")
|
415
519
|
#=> [ {"content" => "Patination01"},
|
416
|
-
|
417
|
-
|
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.
|
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.
|
547
|
+
node.scrape("http://yasuri.example.tac42.net/page01.html")
|
447
548
|
|
448
549
|
#=> [ {"title" => "Page01",
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
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.
|
561
|
+
node.scrape("http://yasuri.example.tac42.net/page01.html")
|
461
562
|
|
462
563
|
#=> [ "Page01",
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
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
|
+
```
|