目的
Pythonを使ってスクレイピング(見出しなど)してきて、Twitterで投稿する。
(今更だがTwitter APIを使ってみたかった)
参考記事
- スクレイピング関連
- PythonのWebスクレイピングでproxy経由でのhttpsアクセスがrequestsを使ったら簡単だった
- PythonとBeautiful Soupでスクレイピング
- BeautifulSoupメモ
- Twitter関連
- 素人が今更Twitter APIで遊ぶ 〜その1〜
- Twitterのbotをpythonで作成する手順
- PythonでTwitter API を利用していろいろ遊んでみる
- RequestsとBeautiful Soupでのスクレイピング時に文字化けを減らす
追記
- 2019/3/28 ツイートをする
post
メソッドの引数**kwargs
について更新しました。
スクレイピング
ヤ〇ーニュースから情報を取得してくる
#ヤ〇ーニュースから情報持ってくる
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」になっており、欲しい部分を上手く取れた。
TwitterAPIを利用して、つぶやくことが目的。
まずはTwitter Developerアカウントを作った。
ページ上部の参考記事にもあるが、こちらを参考に作成した。
アクセストークンなどを取得して、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認証のメソッドが引数にkey
とsecret_key
しかとらない
→つまりTwitterのアクセストークンが入らないから使えない、、、
一方でrequest_oauthlibライブラリだとOAuth1Session(key, secret, token, token_secret)
でトークンも含めて認証できた。
しかし、投稿をしてくれるpost
メソッドにproxies
という引数がない、、、
→認証できてもプロキシ突破できず、投稿できなくね?
そこで、post
メソッドの定義をみたら、引数に**kwargs
なるものが。なんやそれ?
:param \*\*kwargs: Optional arguments that ``request`` takes.
他に引数増やしてもいいよってことらしい。
ということで、引数にproxies=~~
を足してみた。
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
メソッドを見てみると↓だった。
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
型でプロキシ情報渡せば良いらしい。
下の感じで作りました。
#プロキシ設定
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'}
まとめ
意外と時間がかかったが、面白かった!!
次はスクレイピング→ディープラーニングにもっていきたい。
他人の記事を探してやってみるのもいいけど、やっぱり定義をみれば一発でわかりますね!