- はじめに -
この記事は、Xonsh Advent Calendar 2017 - Qiita 25日目最終日の記事です。
本記事では、PythonスクリプトでHatenaブックマークのホットエントリのリストを取得、xonshへ表示する内容を記載します。
追記:2018/11/23
ptk 2.xでは、本記事のコードが動作しないため、移行のための記事とrepositoryを公開しています。
vaaaaaanquish.hatenablog.com
- Hatenaホットエントリの取得 -
はてなにはブログの投稿や取得、はてブ数の取得等のAPIが用意されています。
はてなブックマークドキュメント一覧 - Hatena Developer Center
上記を見る限り、Hatenaホットエントリを取得することは出来ないので、requestsでスクレイピングしてくる必要がありそうです。
import requests import bs4 # ホットエントリページの取得、解析 res = requests.get("http://b.hatena.ne.jp/hotentry") bs_res = bs4.BeautifulSoup(res.text, "lxml") # はてブ数とタイトルの取得 hotentry = [] for x in bs_res.findAll("li", attrs={"class":"entry-unit"}): a_tag = x.find("a", attrs={"class":"entry-link"}) if a_tag is not None: hotentry.append((x.find("span").text, a_tag.attrs["title"], a_tag.attrs["href"])) # はてブ数でソート hotentry = sorted(hotentry, key=lambda x:int(x[0]), reverse=True) # 表示 for x in hotentry: print('{} || {} \n {}'.format(x[0], x[1], x[2]))
上記コードでクローリングしてきた結果が以下のように出ます
1635 || Pythonの学び方と,読むべき本を体系化しました2018〜初心者から上級者まで - Lean Baseball http://shinyorke.hatenablog.com/entry/python2018 627 || コンピューターで全漢字使用可に 6万字コード化 | NHKニュース https://www3.nhk.or.jp/news/html/20171224/k10011270111000.html 567 || 日本テレビのみなさまへ、生活保護についての悪意のある番組放送はやめてください(大西連) - 個人 - Yahoo!ニュース https://news.yahoo.co.jp/byline/ohnishiren/20171224-00079667/ 534 || アニメ犯罪を追う海外ドラマ「ANIME CRIMES DIVISION」がカオスすぎて面白い「お前は地下遊戯王の危険さをわかっていない」 - Togetter https://togetter.com/li/1183065 534 || みずほ銀行、人事評価と結びついた金融商品の押し売りの実態がNHKより流出 : 市況かぶ全力2階建 http://kabumatome.doorblog.jp/archives/65905156.html ...
常々良さそうです。
この結果を結果を使っていきたい。
- xonshのセレクタで選択したらBrowserで開く -
以下の記事で、python prompt toolkit (ptk)を利用した、シェル上で対話的選択する方法を記載しました。
これを利用して、xonshではてなホットエントリを取得してセレクトし、Browserで開くスクリプトとしてxonsh向けに書いてみます。
# -*- coding: utf-8 -*- from __future__ import unicode_literals from prompt_toolkit.application import Application from prompt_toolkit.buffer import Buffer from prompt_toolkit.document import Document from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.key_binding.manager import KeyBindingManager from prompt_toolkit.keys import Keys from prompt_toolkit.layout.containers import Window from prompt_toolkit.layout.controls import BufferControl from prompt_toolkit.layout.margins import ScrollbarMargin from prompt_toolkit.shortcuts import create_eventloop from prompt_toolkit.filters import IsDone from prompt_toolkit.layout.controls import TokenListControl from prompt_toolkit.layout.containers import ConditionalContainer, ScrollOffsets, VSplit, HSplit from prompt_toolkit.layout.screen import Char from prompt_toolkit.layout.dimension import LayoutDimension as D from prompt_toolkit.mouse_events import MouseEventTypes from prompt_toolkit.token import Token from prompt_toolkit.styles import style_from_dict import webbrowser import requests import bs4 def _get_hotentry(): res = requests.get("http://b.hatena.ne.jp/hotentry") bs_res = bs4.BeautifulSoup(res.text, "lxml") hotentry = [] for x in bs_res.findAll("li", attrs={"class":"entry-unit"}): a_tag = x.find("a", attrs={"class":"entry-link"}) if a_tag is not None: hotentry.append((x.find("span").text, a_tag.attrs["title"], a_tag.attrs["href"])) hotentry = sorted(hotentry, key=lambda x:int(x[0]), reverse=True) hotentry = [('{} || {}'.format(x[0], x[1]), x[2]) for x in hotentry] return hotentry def _open_url(url): webbrowser.open(url) def _if_mousedown(handler): def handle_if_mouse_down(cli, mouse_event): if mouse_event.event_type == MouseEventTypes.MOUSE_DOWN: return handler(cli, mouse_event) else: return NotImplemented return handle_if_mouse_down class InquirerControl(TokenListControl): selected_option_index = 0 answered = False choices = TokenListControl def __init__(self, hotentrys, **kwargs): self.choices = [x[0] for x in hotentrys] self.urls = [x[1] for x in hotentrys] super(InquirerControl, self).__init__(self._get_choice_tokens, **kwargs) @property def choice_count(self): return len(self.choices) def _get_choice_tokens(self, cli): tokens = [] T = Token def append(index, label): selected = (index == self.selected_option_index) @_if_mousedown def select_item(cli, mouse_event): self.selected_option_index = index self.answered = True cli.set_return_value(None) token = T.Selected if selected else T tokens.append((T.Selected if selected else T, ' > ' if selected else ' ')) if selected: tokens.append((Token.SetCursorPosition, '')) tokens.append((T.Selected if selected else T, '%-24s' % label, select_item)) tokens.append((T, '\n')) for i, choice in enumerate(self.choices): append(i, choice) tokens.pop() # Remove last newline. return tokens def get_selection(self): return self.choices[self.selected_option_index], self.urls[self.selected_option_index] def _hotentry(): hotentry = _get_hotentry() ic = InquirerControl(hotentry) def __get_prompt_tokens(cli): tokens = [] T = Token tokens.append((Token.QuestionMark, '?')) tokens.append((Token.Question, ' hotentrys ')) if ic.answered: tokens.append((Token.Answer, ' ' + ic.get_selection()[0])) _open_url(ic.get_selection()[1]) else: tokens.append((Token.Instruction, ' (Use arrow keys)')) return tokens layout = HSplit([ Window(height=D.exact(1), content=TokenListControl(__get_prompt_tokens, align_center=False)), ConditionalContainer( Window( ic, width=D.exact(43), height=D(min=3), scroll_offsets=ScrollOffsets(top=1, bottom=1)), filter=~IsDone())]) manager = KeyBindingManager.for_prompt() @manager.registry.add_binding(Keys.ControlQ, eager=True) @manager.registry.add_binding(Keys.ControlC, eager=True) def _(event): event.cli.set_return_value(None) @manager.registry.add_binding(Keys.Down, eager=True) def move_cursor_down(event): ic.selected_option_index = ( (ic.selected_option_index + 1) % ic.choice_count) @manager.registry.add_binding(Keys.Up, eager=True) def move_cursor_up(event): ic.selected_option_index = ( (ic.selected_option_index - 1) % ic.choice_count) @manager.registry.add_binding(Keys.Enter, eager=True) def set_answer(event): ic.answered = True event.cli.set_return_value(None) inquirer_style = style_from_dict({ Token.QuestionMark: '#5F819D', Token.Selected: '#FF9D00', # AWS orange Token.Instruction: '', # default Token.Answer: '#FF9D00 bold', # AWS orange Token.Question: 'bold', }) _app = Application( layout=layout, #buffers=buffers, key_bindings_registry=manager.registry, mouse_support=True, #use_alternate_screen=True style=inquirer_style ) _eventloop = create_eventloop() try: cli = CommandLineInterface(application=_app, eventloop=_eventloop) cli.run(reset_current_buffer=False) finally: _eventloop.close() aliases["hotentry"] = _hotentry
これでxonsh上でのhotentryコマンドが作れました。
いざ実行してみます。
クールっぽい。
Browserで開くならあんまり意味ない気もしなくもないので、textだけでよしなに取ってくるマンを作って組み合わせたい所ですね。
- おわりに -
よさそうなセレクタの使い方ができました。
今年はWebスクレイピング記事で技術記事歴代一位のはてブ数を獲得した年でもあるので、良い記事で締めくくれていると自負しています。
アドベントカレンダーも盛り上がって、xonsh関連の日本語情報はかなり充実したと思います。
Xonsh Advent Calendar 2017 - Qiita
皆さんxonshでよい年末を。