Edited at

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


目的

Pythonを使ってスクレイピング(見出しなど)してきて、Twitterで投稿する。

(今更だがTwitter APIを使ってみたかった)


参考記事


追記


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


スクレイピング

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


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://~~~~~"))


↑を実行したらエラーになった


エラー内容

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の入れ子はできないらしい。。。

ということで、以下のコードで実行!!


修正➀

newslist=soup.find_all('ul', attrs={'class': 'listFeed'})

title=news.find("a").find("dt").text -->見出し
link=news.find("a").attrs["href"] -->リンク

一応ほしい情報は持ってこれた。

ただ、find()だと1件だけとってきて終了になる。。。

更に修正した。


修正➁

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の1番目の要素にhtmlが全部入ってるイメージ)

対して、list型だと1番目の要素は「html」になっており、欲しい部分を上手く取れた。


Twitter

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

まずはTwitter Developerアカウントを作った。

ページ上部の参考記事にもあるが、こちらを参考に作成した。

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


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認証


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


ツイッター投稿

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認証のメソッドが引数にkeysecret_keyしかとらない

→つまりTwitterのアクセストークンが入らないから使えない、、、

一方でrequest_oauthlibライブラリだとOAuth1Session(key, secret, token, token_secret)でトークンも含めて認証できた。

しかし、投稿をしてくれるpost メソッドにproxiesという引数がない、、、

→認証できてもプロキシ突破できず、投稿できなくね?

そこで、postメソッドの定義をみたら、引数に**kwargsなるものが。なんやそれ?

:param \*\*kwargs: Optional arguments thatrequesttakes.

他に引数増やしてもいいよってことらしい。

ということで、引数にproxies=~~を足してみた。


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メソッドを見てみると↓だった。


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型でプロキシ情報渡せば良いらしい。

下の感じで作りました。


プロキシ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__)を実行すると詳しい内容が出てくる。

import pprint

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


実行結果

<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'}


まとめ

意外と時間がかかったが、面白かった!!

次はスクレイピング→ディープラーニングにもっていきたい。

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