Pythonでスクレイピング→Twitterで投稿してみた 2018/11/12現在

# 目的
Pythonを使ってスクレイピング(見出しなど)してきて、Twitterで投稿する。
(今更だがTwitter APIを使ってみたかった)

# 参考記事
- スクレイピング関連
 - [PythonのWebスクレイピングでproxy経由でのhttpsアクセスがrequestsを使ったら簡単だった](https://qiita.com/koharite/items/731fcf5146c7b0c4e800)
 - [PythonとBeautiful Soupでスクレイピング](https://qiita.com/itkr/items/513318a9b5b92bd56185)
 - [BeautifulSoupメモ](https://qiita.com/kawai_yusuke315/items/3c1876c04034b6552e3d)
- Twitter関連
 - [素人が今更Twitter APIで遊ぶ　〜その１〜](https://qiita.com/masahiro_isono/items/a6cfdd073686100f0ef1)
 - [Twitterのbotをpythonで作成する手順](https://qiita.com/yuki_bg/items/96a1608aa3f3225386b6)
 - [PythonでTwitter API を利用していろいろ遊んでみる](https://qiita.com/bakira/items/00743d10ec42993f85eb)
 - [RequestsとBeautiful Soupでのスクレイピング時に文字化けを減らす](https://orangain.hatenablog.com/entry/encoding-in-requests-and-beautiful-soup)

## 追記
- 2019/3/28 ツイートをする`post`メソッドの引数`**kwargs`について更新しました。

## スクレイピング

ヤ〇ーニュースから情報を取得してくる

~~~python:BeautifulSoupテスト
#ヤ〇ーニュースから情報持ってくる
target_url = 'https://~~~~~~co.jp/'
#プロキシ設定が必要なら引数にproxiesを入れておく
req = requests.get(target_url,proxies=proxy_settings.proxy_dict)
soup = BeautifulSoup(req.text,"html.parser")

↑`req.text`だと日本語が文字化けするときがある。`req.content`にすると解消した

#欲しい部分をfind_allで抽出する
newslist=soup.find_all('ul', attrs={'class': 'listFeed'})
linklist=list.find_all(href=re.compile("https://~~~~~"))
~~~
↑を実行したらエラーになった

~~~python:エラー内容
AttributeError: ResultSet object has no attribute 'find_all'. 
You're probably treating a list of items like a single item. 
Did you call find_all() when you meant to call find()?
~~~

調べたところ、どうやらfind_allの入れ子はできないらしい。。。
ということで、以下のコードで実行！！


~~~python:修正➀
newslist=soup.find_all('ul', attrs={'class': 'listFeed'})
title=news.find("a").find("dt").text -->見出し
link=news.find("a").attrs["href"]    -->リンク
~~~

一応ほしい情報は持ってこれた。
ただ、`find()`だと１件だけとってきて終了になる。。。
更に修正した。

~~~python:修正➁
newslist=soup.select(".listFeedWrap")

#コードが冗長になっている
for i in range(len(newslist)):
    title=newslist[i].find(attrs={"class":"titl"}).text  -->見出し
    link=newslist[i].find("a").attrs["href"]             -->リンク

#こちらのほうが望ましい
for news in newslist:
    title=news.find(attrs={"class":"titl"}).text         -->見出し
    link=news.find("a").attrs["href"]                    -->リンク
~~~

これで見出しとリンクがちゃんととれた！
for文の記載は`for i in range(~`だと冗長らしい。

ちなみに
修正➀では、`newslist=soup.find_all('ul', attrs={'class': 'listFeed'})`だが
修正➁では、`newslist=soup.select(".listFeedWrap")`にしました。

➀の`find_all()`だと、戻り値が**element.Resultset型**で、
➁の`select()`だと、戻り値が**list型**になる。

いろいろ試したが、**element.Resultset型**だとfor文を使ったときに細かく情報が取れなかった。
(listの１番目の要素にhtmlが全部入ってるイメージ)
対して、**list型**だと１番目の要素は「html」になっており、欲しい部分を上手く取れた。

## Twitter
TwitterAPIを利用して、つぶやくことが目的。

まずはTwitter Developerアカウントを作った。
ページ上部の参考記事にもあるが、[**こちら**](https://qiita.com/bakira/items/00743d10ec42993f85eb)を参考に作成した。

アクセストークンなどを取得して、OAuth認証をした

~~~python:OAuth認証
from requests_oauthlib import OAuth1Session
import proxy_settings

class OAuth認証:
    
    #トークンの取得
    key=consumer_key
    secret=consumer_secret
    token=access_token
    token_secret=access_token_secret
    auth = OAuth1Session(key, secret, token, token_secret)  #OAuth認証
~~~

認証がうまくいったので、投稿してみる

~~~python:ツイッター投稿
def post(self,text):
        #tweet内容をポスト
        url="https://api.twitter.com/1.1/statuses/update.json"
        param = {"status" : text}
        res=self.auth.post(url,params=param)
        
        #post出来たかどうか
        if res.status_code == 200:
            print("Posted:"+text)
        else:
            print("Failed : %d"% res.status_code)
~~~
投稿できない！！プロキシにはじかれているっぽい。
実はここが結構はまったところ。

ライブラリのソースを見たら、
**requests**ライブラリだと`requests.get(url, proxies=~~~)`でプロキシ突破して接続できた。
ただ、OAuth認証のメソッドが引数に`key`と`secret_key`しかとらない
→つまりTwitterのアクセストークンが入らないから使えない、、、

一方で**request_oauthlib**ライブラリだと`OAuth1Session(key, secret, token, token_secret)`でトークンも含めて認証できた。
しかし、投稿をしてくれる`post` メソッドに`proxies`という引数がない、、、
→認証できてもプロキシ突破できず、投稿できなくね？

そこで、`post`メソッドの定義をみたら、引数に`**kwargs`なるものが。なんやそれ？
`:param \*\*kwargs: Optional arguments that ``request`` takes.`
他に引数増やしてもいいよってことらしい。
ということで、引数に`proxies=~~`を足してみた。

~~~python:postメソッド定義
def post(self, url, data=None, json=None, **kwargs):
        r"""Sends a POST request. Returns :class:`Response` object.

        :param url: URL for the new :class:`Request` object.
        :param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
        :param json: (optional) json to send in the body of the :class:`Request`.
        :param \*\*kwargs: Optional arguments that ``request`` takes.
         ↑requestメソッドの引数にあるオプションは追加していいですよのこと
        :rtype: requests.Response
        """
        return self.request('POST', url, data=data, json=json, **kwargs)
~~~

#### ～2019/3/28追加～　**kwargsについて　
`**kwargs`って何者なんだと思い、`request`メソッドを見てみると↓だった。

```python:requestメソッド

def request(self, method, url,
            params=None, data=None, headers=None, cookies=None, files=None,
            auth=None, timeout=None, allow_redirects=True, proxies=None,
            hooks=None, stream=None, verify=None, cert=None, json=None):
         """
         ～途中省略～
         :param proxies: (optional) Dictionary mapping protocol or protocol and
            hostname to the URL of the proxy.
         """
```

`Dictionary`型でプロキシ情報渡せば良いらしい。
下の感じで作りました。

```python:プロキシdict

#プロキシ設定
proxy_dict = {
    "http": "http://hogehoge",
    "https": "https://hogehoge"
}

```

というわけで`post`実行時に`proxies=プロキシdict`を渡すと、、、
無事に投稿できました！！！
いやぁ～長かった。
ちゃんとできて一安心です。

BeautifulSoupについて少し調べたので↓に書きます。

# BeautifulSoupとは

Pythonで使えるライブラリで
対象URLのサイトからhtmlをとってこれる優れもの。
いろんな要素・属性を指定できる。

かなりいろいろなことが出来るっぽいが、そこまで詳しく調べてない。。

### BeautifulSoup API

|説明|コード例|
|---|---|
|子要素|soup.head|
|タグ全検索|soup.find_all('li')|
|1件検索|soup.find('li')|
|属性検索|soup.find('li', href="")|
|class検索|soup.find('a', class_="first")|
|属性取得|first_link_element['href']|
|テキスト要素|first_link_element.string|
|親要素|first_link_element.parent|

### BeautifulSoup cssセレクタ チートシート

|説明|コード例|
|---|---|
|タグ検索|soup.select('li')|
|1件検索|soup.select_one('li')|
|属性検索|soup.select('a[href=""]')|
|属性存在|soup.select('a[data])|
|class検索|soup.select('a.first')|


ちなみに、`pprint.pprint(news.__dict__)`を実行すると詳しい内容が出てくる。

~~~python:
import pprint

print(type(news))
pprint.pprint(news.__dict__)
~~~

~~~python:実行結果
<class 'bs4.element.Tag'>
{'attrs': {'class': ['listFeedWrap']},
 'can_be_empty_element': False,
 'contents': ['\n',.....],
 'hidden': False,
 'known_xml': False,
 'name': 'li',
 'namespace': None,
 'next_element': '\n',
 'next_sibling': '\n',
 'parent': <ul class="listFeed">.....],
 'parser_class': <class 'bs4.BeautifulSoup'>,
 'prefix': None,
 'preserve_whitespace_tags': {'pre', 'textarea'},
 'previous_element': '\n',
 'previous_sibling': '\n'}
~~~

# まとめ

意外と時間がかかったが、面白かった！！
次はスクレイピング→ディープラーニングにもっていきたい。

他人の記事を探してやってみるのもいいけど、やっぱり定義をみれば一発でわかりますね！