python超初心者がスクレイピングしてみる

## スクレイピングとは

「スクレイピング」って言葉を出すと、だいたい「クローリング」「スクレイピング」の2つが出てきます。
自分も混同していたので一度整理しておきます。

* クローリング
	* web上に公開されてるページのリンクを辿って、その辿り着いた先のwebページをダウンロードする作業
* スクレイピング
	* ダウンロードしたwebページから、自分が欲しい情報(一部)を抜き出す作業

って事で、例えば、将棋連盟のページから、自分の好きな棋士のタイトルを抜き出してくる。
とかは「スクレイピング」という訳です。

## scrapy

じゃあ実際にスクレイピングしてみよう。と思うと、私はこれまでPHPしか使ってこなかったので、
Goutteとかを使って一生懸命ページの中から欲しい情報を抜き出してみました。

で、最近入門したPythonでは、どうやら[Scrapy](https://scrapy.org/)というライブラリ(フレームワーク?)があって、これを使うといとも簡単にスクレイピングが出来るという事を知りました。

なので、今回はこれを使って将棋連盟のページから、大好きな棋士達の情報を自分なりに収集してみようと思います。

## インストール

```
$ pip install scrapy
```

完

## チュートリアル

さて、私は本当にPythonは全くわからない超初心者なので、とりあえずチュートリアルを順を追ってやってみて、感覚を掴んでみようと思います。

ドキュメントの中にチュートリアルコーナがありました。
https://docs.scrapy.org/en/latest/intro/tutorial.html

英語だけど何とかなりそう。

## チュートリアルに書いてある作業の順序

1. 新しくScrapyのプロジェクトをつくる
2. サイトをクローリングして、必要なデータを抜き出す為の[spider](https://docs.scrapy.org/en/latest/topics/spiders.html#topics-spiders)を書きましょう
3. コマンドラインから、抜き出した情報をアウトプットしましょう
4. spiderを、リンクを辿ってく感じに変えてみましょう(英語わからんかった)
5. spiderの引数を使ってみよう

何かこんな順番でやってくぽいです。

## 1. 新しくScrapyのプロジェクトをつくる

`scrapy startproject tutorial`

これで良いらしい。

```
[vagrant@localhost test]$ scrapy startproject tutorial
New Scrapy project 'tutorial', using template directory '/usr/lib64/python3.5/site-packages/scrapy/templates/project', created in:
    /home/vagrant/test/tutorial

You can start your first spider with:
    cd tutorial
    scrapy genspider example example.com
    
[vagrant@localhost test]$ ll
合計 0
drwxr-xr-x 3 vagrant vagrant 38  4月 16 04:15 tutorial
```

tutorialというディレクトリが出来た！

で、この中に色々とあるのですが、ドキュメントによると各ファイルは下のような役割があるそう。

```
tutorial/
    scrapy.cfg            # デプロイの設定ファイル

    tutorial/             # project's Python module, you'll import your code from here
        __init__.py

        items.py          # project items definition file

        pipelines.py      # project pipelines file

        settings.py       # project settings file

        spiders/          # a directory where you'll later put your spiders
            __init__.py
```

デプロイの設定ファイル以外は何かよくわからなかった笑

## 2. サイトをクローリングして、必要なデータを抜き出す為の[spider](https://docs.scrapy.org/en/latest/topics/spiders.html#topics-spiders)を書きましょう

`tutorial/spides/`配下に`quotes_spider.py`というファイルを作ってコピペしろ的なのがあるので作ります。

```
[vagrant@localhost tutorial]$ vi tutorial/spiders/quotes_spider.py
```

```python
import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    def start_requests(self):
        urls = [
            'http://quotes.toscrape.com/page/1/',
            'http://quotes.toscrape.com/page/2/',
        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        page = response.url.split("/")[-2]
        filename = 'quotes-%s.html' % page
        with open(filename, 'wb') as f:
            f.write(response.body)
        self.log('Saved file %s' % filename)
```

* name 
	* spiderの識別子？同じプロジェクト内でユニークにする必要があるらしい
* start_requests()
	* ここが、クローリングの開始URL。イテレータブルなRequestsを返しなさい的な事が書いてある
* parse()
	* それぞれのページがダウンロード出来たら呼び出される？
	* で、この第二引数のresponseは[TextResponse](https://docs.scrapy.org/en/latest/topics/request-response.html#scrapy.http.TextResponse)のインスタンスが入ってくるぽい
	* ページ内の要素をselector、xpath, cssとかで指定して抜き出すようなメソッドを持っていそうだ

## 3. コマンドラインから、抜き出した情報をアウトプットしましょう

`scrapy crawl quotes`

これで行けるらしい。

何か色々出た後に、`quotes-1.html`と`quotes-2.html`が出来てた

```
[vagrant@localhost tutorial]$ ll
合計 32
-rw-rw-r-- 1 vagrant vagrant 11053  4月 16 04:27 quotes-1.html
-rw-rw-r-- 1 vagrant vagrant 13734  4月 16 04:27 quotes-2.html
-rw-r--r-- 1 vagrant vagrant   260  4月 16 04:15 scrapy.cfg
drwxr-xr-x 4 vagrant vagrant   129  4月 16 04:15 tutorial
```

私はここで「コマンドラインから抜き出した情報をアウトプットしましょう」と書きましたが、
実際に、parseメソッドの中身を見ると、↓のような事をしてるだけだった

- クローリングしたサイトのURLから数字の部分を抜き出して
- `quotes-%s.html`の%s部分にこの数字を当て込んで
- 最後にこのファイルにresponse(TextResponse)のbodyを入れて保存

#### start_requestsメソッドはもうちょい簡単に書ける

結局、このメソッドは、最終的に`scrapy.Request`のオブジェクトを返してるだけだが、これを`start_urls`と書くだけで実現出来るらしい。

```python
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
         'http://quotes.toscrape.com/page/2/',
    ]
```

わざわざ`start_requests`メソッドを定義しなくてもこれでOK

## いよいよデータを抜き出してみる

チュートリアルでは、「scrapyが実際にどうやって抜き出していくかを学ぶには、`scrapy shell`を使うと良いよ」と書いてあります。

早速やってみる

```
[vagrant@localhost tutorial]$ scrapy shell 'http://quotes.toscrape.com/page/1/'

... 中略 ...

2017-04-16 04:36:51 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/page/1/> (referer: None)
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x7fbb13dd0080>
[s]   item       {}
[s]   request    <GET http://quotes.toscrape.com/page/1/>
[s]   response   <200 http://quotes.toscrape.com/page/1/>
[s]   settings   <scrapy.settings.Settings object at 0x7fbb129308d0>
[s]   spider     <DefaultSpider 'default' at 0x7fbb11f14828>
[s] Useful shortcuts:
[s]   fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)
[s]   fetch(req)                  Fetch a scrapy.Request and update local objects
[s]   shelp()           Shell help (print this help)
[s]   view(response)    View response in a browser
```

#### まずはcssを使って要素を抜き出して見る

```
>>> response.css('title')
[<Selector xpath='descendant-or-self::title' data='<title>Quotes to Scrape</title>'>]
```

お、何かタイトル要素っぽいのが抜き出せてるっぽい。

この`reponse.css(xxx)`した時に返ってくるのは、[SelectorList](https://docs.scrapy.org/en/latest/topics/selectors.html#scrapy.selector.SelectorList)という、XMLとかHTMLをラップしてるオブジェクト。
で、ここからさらにデータを抜き出す。という事も出来る。
試しにタイトルのtextを抜き出す。

```
>>> response.css('title::text').extract()
['Quotes to Scrape']
```

* `::text`と付けると、この<title>タグの中からtext要素だけを抜き出すという意味
* これをつけなければ、<title>タグのまんまが取れる

```
>>> response.css('title').extract()
['<title>Quotes to Scrape</title>']
```

<title>タグ毎取れてるのが分かる

#### 要素の1つを取得する

extractをすると、[SelectorList](https://docs.scrapy.org/en/latest/topics/selectors.html#scrapy.selector.SelectorList)を返してくれるので、基本はlist型が返ってくる。
(だから上のは全部`[]`で囲われてたのね)

この中で、特定の1つを取得する場合は、listの番号を指定するか、`extract_first`で最初の要素を取得する。

* extract_firstを使う

```
>>> response.css('title::text').extract_first()
'Quotes to Scrape'
```

* listの番号指定する

```
>>> response.css('title::text')[0].extract()
'Quotes to Scrape'

## titleはこのwebページ内で1つしかないから、2番目を指定すると怒られる
>>> response.css('title::text')[1].extract()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/usr/lib/python3.5/site-packages/parsel/selector.py", line 58, in __getitem__
    o = super(SelectorList, self).__getitem__(pos)
IndexError: list index out of range
```

## xpathを使って抜き出す

xpathってなんだ？って思ってたんですが、@merrillさんの記事が非常に分かりやすかったです。

http://qiita.com/merrill/items/aa612e6e865c1701f43b

HTMLの中から、`tbodyの中の、4つめのtdの中のatag`みたいな指定が出来るっぽい。

早速今回の例で使ってみるとこんな感じ

```
>>> response.xpath('//title')
[<Selector xpath='//title' data='<title>Quotes to Scrape</title>'>]

>>> response.xpath('//title/text()').extract_first()
'Quotes to Scrape'
```

### もっと抜き出してみる

今スクレイピング対象としてる、http://quotes.toscrape.com/page/1/の、テキストの部分とauthorを抜き出してみる。

![スクリーンショット 2017-04-16 12.12.55.png](https://qiita-image-store.s3.amazonaws.com/0/23276/c047e05f-dd6c-b813-9c2a-78bbacc49db1.png "スクリーンショット 2017-04-16 12.12.55.png")



まずは1つめのdivをquoteという変数に入れとく

```
>>> quote = response.css("div.quote")[0]
```

```
>>> title = quote.css("span.text::text").extract_first()
>>> title
'“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'
```

テキスト部分の抜き出しに成功

* authorもチャレンジ

```
>>> autor = quote.css("small.author::text").extract_first()
>>> autor
'Albert Einstein'
```

めちゃくちゃ簡単だ。

* tag一覧も取得してみる

```
>>> tags = quote.css("div.tags a.tag::text").extract()
>>> tags
['change', 'deep-thoughts', 'thinking', 'world']
```

ちゃんとlist型で抜き出せてる

```
>>> for quote in response.css("div.quote"):
>>> text = quote.css("span.text::text").extract_first()
>>> author = quote.css("small.author::text").extract_first()
>>> tags = quote.css("div.tags a.tag::text").extract()
>>> print(dict(text=text, author=author, tags=tags))
{'tags': ['change', 'deep-thoughts', 'thinking', 'world'], 'author': 'Albert Einstein', 'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”'}
```

## これをshellじゃなくてspiderでやってみる

```
import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    start_urls = [
      'http://quotes.toscrape.com/page/1/',
      'http://quotes.toscrape.com/page/2/',
    ]

    def parse(self, response):
        for quote in response.css("div.quote"):
          yield {
            'text' : quote.css('span.text::text').extract_first(),
            'author' : quote.css('small.author::text').extract_first(),
            'tags' : quote.css('div.tags a.tag::text').extract()
          }
```

こんな感じに書き換えて実行してみます。

```
[vagrant@localhost tutorial]$ scrapy crawl quotes
2017-04-16 05:27:09 [scrapy.utils.log] INFO: Scrapy 1.3.3 started (bot: tutorial)
2017-04-16 05:27:09 [scrapy.utils.log] INFO: Overridden settings: {'NEWSPIDER_MODULE': 'tutorial.spiders', 'BOT_NAME': 'tutorial', 'SPIDER_MODULES': ['tutorial.spiders'], 'ROBOTSTXT_OBEY': True}

...中略...

{'author': 'Albert Einstein', 'text': '“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”', 'tags': ['change', 'deep-thoughts', 'thinking', 'world']}
2017-04-16 05:27:11 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'author': 'J.K. Rowling', 'text': '“It is our choices, Harry, that show what we truly are, far more than our abilities.”', 'tags': ['abilities', 'choices']}
2017-04-16 05:27:11 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'author': 'Albert Einstein', 'text': '“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”', 'tags': ['inspirational', 'life', 'live', 'miracle', 'miracles']}
2017-04-16 05:27:11 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'author': 'Jane Austen', 'text': '“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”', 'tags': ['aliteracy', 'books', 'classic', 'humor']}
2017-04-16 05:27:11 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'author': 'Marilyn Monroe', 'text': "“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”", 'tags': ['be-yourself', 'inspirational']}
2017-04-16 05:27:11 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'author': 'Albert Einstein', 'text': '“Try not to become a man of success. Rather become a man of value.”', 'tags': ['adulthood', 'success', 'value']}
2017-04-16 05:27:11 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>
{'author': 'André Gide', 'text': '“It is better to be hated for what you are than to be loved for what you are not.”', 'tags': ['life', 'love']}
2017-04-16 05:27:11 [scrapy.core.scraper] DEBUG: Scraped from <200 http://quotes.toscrape.com/page/1/>

...後略...
```

色々出てるが、抜き出せてるっぽい。

**ファイルに出して見る**

```
[vagrant@localhost tutorial]$ scrapy crawl quotes -o result.json
```

結果を見よう

```
[vagrant@localhost tutorial]$ cat result.json
[
{"tags": ["change", "deep-thoughts", "thinking", "world"], "text": "\u201cThe world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.\u201d", "author": "Albert Einstein"},
{"tags": ["abilities", "choices"], "text": "\u201cIt is our choices, Harry, that show what we truly are, far more than our abilities.\u201d", "author": "J.K. Rowling"},
{"tags": ["inspirational", "life", "live", "miracle", "miracles"], "text": "\u201cThere are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.\u201d", "author": "Albert Einstein"},
{"tags": ["aliteracy", "books", "classic", "humor"], "text": "\u201cThe person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.\u201d", "author": "Jane Austen"},
{"tags": ["be-yourself", "inspirational"], "text": "\u201cImperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.\u201d", "author": "Marilyn Monroe"},
{"tags": ["adulthood", "success", "value"], "text": "\u201cTry not to become a man of success. Rather become a man of value.\u201d", "author": "Albert Einstein"},
{"tags": ["life", "love"], "text": "\u201cIt is better to be hated for what you are than to be loved for what you are not.\u201d", "author": "Andr\u00e9 Gide"},
{"tags": ["edison", "failure", "inspirational", "paraphrased"], "text": "\u201cI have not failed. I've just found 10,000 ways that won't work.\u201d", "author": "Thomas A. Edison"},
{"tags": ["misattributed-eleanor-roosevelt"], "text": "\u201cA woman is like a tea bag; you never know how strong it is until it's in hot water.\u201d", "author": "Eleanor Roosevelt"},
{"tags": ["humor", "obvious", "simile"], "text": "\u201cA day without sunshine is like, you know, night.\u201d", "author": "Steve Martin"},
{"tags": ["friends", "heartbreak", "inspirational", "life", "love", "sisters"], "text": "\u201cThis life is what you make it. No matter what, you're going to mess up sometimes, it's a universal truth. But the good part is you get to decide how you're going to mess it up. Girls will be your friends - they'll act like it anyway. But just remember, some come, some go. The ones that stay with you through everything - they're your true best friends. Don't let go of them. Also remember, sisters make the best friends in the world. As for lovers, well, they'll come and go too. And baby, I hate to say it, most of them - actually pretty much all of them are going to break your heart, but you can't give up because if you give up, you'll never find your soulmate. You'll never find that half who makes you whole and that goes for everything. Just because you fail once, doesn't mean you're gonna fail at everything. Keep trying, hold on, and always, always, always believe in yourself, because if you don't, then who will, sweetie? So keep your head high, keep your chin up, and most importantly, keep smiling, because life's a beautiful thing and there's so much to smile about.\u201d", "author": "Marilyn Monroe"},
{"tags": ["courage", "friends"], "text": "\u201cIt takes a great deal of bravery to stand up to our enemies, but just as much to stand up to our friends.\u201d", "author": "J.K. Rowling"},
{"tags": ["simplicity", "understand"], "text": "\u201cIf you can't explain it to a six year old, you don't understand it yourself.\u201d", "author": "Albert Einstein"},
{"tags": ["love"], "text": "\u201cYou may not be her first, her last, or her only. She loved before she may love again. But if she loves you now, what else matters? She's not perfect\u2014you aren't either, and the two of you may never be perfect together but if she can make you laugh, cause you to think twice, and admit to being human and making mistakes, hold onto her and give her the most you can. She may not be thinking about you every second of the day, but she will give you a part of her that she knows you can break\u2014her heart. So don't hurt her, don't change her, don't analyze and don't expect more than she can give. Smile when she makes you happy, let her know when she makes you mad, and miss her when she's not there.\u201d", "author": "Bob Marley"},
{"tags": ["fantasy"], "text": "\u201cI like nonsense, it wakes up the brain cells. Fantasy is a necessary ingredient in living.\u201d", "author": "Dr. Seuss"},
{"tags": ["life", "navigation"], "text": "\u201cI may not have gone where I intended to go, but I think I have ended up where I needed to be.\u201d", "author": "Douglas Adams"},
{"tags": ["activism", "apathy", "hate", "indifference", "inspirational", "love", "opposite", "philosophy"], "text": "\u201cThe opposite of love is not hate, it's indifference. The opposite of art is not ugliness, it's indifference. The opposite of faith is not heresy, it's indifference. And the opposite of life is not death, it's indifference.\u201d", "author": "Elie Wiesel"},
{"tags": ["friendship", "lack-of-friendship", "lack-of-love", "love", "marriage", "unhappy-marriage"], "text": "\u201cIt is not a lack of love, but a lack of friendship that makes unhappy marriages.\u201d", "author": "Friedrich Nietzsche"},
{"tags": ["books", "contentment", "friends", "friendship", "life"], "text": "\u201cGood friends, good books, and a sleepy conscience: this is the ideal life.\u201d", "author": "Mark Twain"},
{"tags": ["fate", "life", "misattributed-john-lennon", "planning", "plans"], "text": "\u201cLife is what happens to us while we are making other plans.\u201d", "author": "Allen Saunders"}
```

ぽいぽい！！！！
めっちゃ楽ｗｗ

## 4. spiderを、リンクを辿ってく感じに変えてみましょう(英語わからんかった)

さて、今はstart_urlsに直接遷移先URLを全て記載してました。
でも、例によって、ページ内の特定のリンクを辿って行って、再帰的に欲しいデータを取得したいという事もあると思います。

そんな時は、リンクのURLを取得して、自分のparseを呼び出してあげれば良さそうです。

```
import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/page/1/',
    ]

    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.css('small.author::text').extract_first(),
                'tags': quote.css('div.tags a.tag::text').extract(),
            }

        next_page = response.css('li.next a::attr(href)').extract_first()
        if next_page is not None:
            next_page = response.urljoin(next_page)
            yield scrapy.Request(next_page, callback=self.parse)

```

こんな感じになります。`next_page`があれば再度巡回するって感じですね。

`urljoin`すると良しなに巡回するURLにしてくれるぽいのかな？

### もっとクローリングして遊んで見よう

ここでは、http://quotes.toscrape.comの、authorの部分にリンクがあるので、それを辿ってさらに情報を取ってみるというチュートリアルが紹介されてます。

```python

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    start_urls = [
      'http://quotes.toscrape.com/',
    ]

    def parse(self, response):
      # authorの詳細ページのリンク取得
      for href in response.css('.author + a::attr(href)').extract():
        yield scrapy.Request(response.urljoin(href), callback=self.parse_author)


      # ページネーションのリンクを取得
      next_page = response.css('li.next a::attr(href)').extract_first()
      if next_page is not NONE:
        next_page = response.urljoin(next_page)
        yield scrapy.Request(next_page, callback=self.parse)

    def parse_author(self, response):
      # 受け取ったクエリでresponseから抽出して、strip(トリムっぽいこと)する
      def extract_with_css(query):
        return response.css(query).extract_first().strip()

      yield {
        'name' : extract_with_css('h3.author-title::text'),
        'birthdate' : extract_with_css('.author-born-date::text'),
        'bio': extract_with_css('.author-description::text'),
      }
```

こんな風にすると、

- 1. ひたすらauthorのリンクを辿って、`parse_author`(名前、生誕日、説明を抜き出す)をする
- 2. ページングが存在してたら、次のページに対して、またparseを行う
- 3. ページングがなくなるまで繰り返す

これだけのものがわずか数十行で書けるとは・・・・

## 5. spiderの引数を使ってみよう

これは使いみちがよくわからんかったのでパス。

## まとめ

- scrapyを使ってプロジェクト作る
- spidersに自分のやりたいことを書く
- リンクを辿ってクローリングも可能
- 抜き出し方も超絶簡単

## 補足 - unicodeされて読めない問題

`-o`でjsonに出力すると、文字列がunicodeされてて読めません。
これは、`[project_name]/settings.py`に、`FEED_EXPORT_ENCODING='utf-8'`を1行追加してあげれば解決出来ます。

## オマケ

棋士のデータをスクレイピングするものを作ってみました。

やったことは

- 将棋連盟の棋士一覧ページを起点にして
- 棋士詳細ページのリンクを辿って
	- `氏名、生年月日、師匠` のデータを抜き出す

実際のコードはこんな感じ(簡単ですねｗ)

```python
import scrapy

class QuotesSpider(scrapy.Spider):
    name = "kisi"

    start_urls = [
      'https://www.shogi.or.jp/player/',
    ]

    def parse(self, response):
      # 棋士の詳細ページのリンク取得
      for href in response.css("p.ttl  a::attr(href)").extract():
        yield scrapy.Request(response.urljoin(href), callback=self.parse_kisi)

    def parse_kisi(self, response):
      def extract_with_xpath(query):
        return response.xpath(query).extract_first().strip()

      yield {
        'name' :  extract_with_xpath('//*[@id="contents"]/div[2]/div/div[2]/div/div/h1/span[1]/text()'),
        'birth' : extract_with_xpath('//*[@id="contents"]/div[2]/div/div[2]/table/tbody/tr[2]/td/text()'),
        'sisho' : extract_with_xpath('//*[@id="contents"]/div[2]/div/div[2]/table/tbody/tr[4]/td/text()'),
      }
```

#### 結果

```
[vagrant@localhost tutorial]$ head kisi.json
[
{"name": "渡辺明", "birth": "1984年4月23日(32歳)", "sisho": "所司和晴七段"},
{"name": "浦野真彦", "birth": "1964年3月14日(53歳)", "sisho": "（故）中井捨吉八段"},
{"name": "泉正樹", "birth": "1961年1月11日(56歳)", "sisho": "関根　茂九段"},
{"name": "土佐浩司", "birth": "1955年3月30日(62歳)", "sisho": "（故）清野静男八段"},
{"name": "神谷広志", "birth": "1961年4月21日(55歳)", "sisho": "(故)廣津久雄九段"},
{"name": "北浜健介", "birth": "1975年12月28日(41歳)", "sisho": "佐伯昌優九段"},
{"name": "阿久津主税", "birth": "1982年6月24日(34歳)", "sisho": "滝　誠一郎八段"},
{"name": "山崎隆之", "birth": "1981年2月14日(36歳)", "sisho": "森　信雄七段"},
{"name": "広瀬章人", "birth": "1987年1月18日(30歳)", "sisho": "勝浦　修九段"},
```

ちゃんと全員分取れてるのが分かります。
本当に簡単です。

## 今後やりたいこと

- 特定のページを起点にして
- 検索条件を指定して
- 検索した結果をルールにもとづいて抽出

また出来たら記事にしてみます。
(てか、yieldもよく分かってないし、デバッグもなれないし、もうちょいpythonの勉強しないと。。)