def _create_table(self, reviews: Tuple[ReviewInfo, ...]) -> str: """レビュー情報一覧から table 要素を作成する Args: reviews (Tuple[ReviewInfo, ...]): レビュー情報一覧 Returns: table タグで囲まれたテキスト """ thead = tag('thead', tag('tr', tag('th', *ReviewInfo._fields))) tr_content_list = list() for review_info in reviews: # レビュー文の処理 review = review_info.review.replace('\n', '<br>') review_info = review_info._replace(review=review) if self.normalize_mode: review = normalize(review) # 列要素を作成 td_content_list = [tag('td', info) for info in review_info] # 行要素を作成 tr = organize_contents(td_content_list, 'tr') tr_content_list.append(tr) tbody = organize_contents(tr_content_list, 'tbody') table_content_list = [thead, tbody] table = organize_contents( table_content_list, 'table', class_='tablesorter tablesorter-{}'.format(TABLE_DESIGN), id=OLD_AND_NEW[1] ) return table
def _make_stars_texts(reviews: Tuple[ReviewInfo, ...], stars_dist: StarsDistribution) -> Tuple[str, ...]: """星評価に関する表示テキストを作成する Args: reviews (Tuple[ReviewInfo, ...]): レビュー情報一覧 stars_dist (StarsDistribution): 星評価分布 Returns: 星の数ごとの h3 要素一覧 """ # 星評価の整理 reviews_df = pandas.DataFrame(reviews) key_dict = OrderedDict() for i, num_stars_field in enumerate(StarsDistribution._fields): key_dict[num_stars_field] = float(i + 1) divided_dict = OrderedDict() eval_star_query_fmt = 'star == {}' for num_stars_field, eval_star in key_dict.items(): eval_star_query = eval_star_query_fmt.format(eval_star) num_stars_df = reviews_df.query(eval_star_query)['star'] divided_dict[num_stars_field] = num_stars_df.count() star_to_text = OrderedDict() for num_stars_field, star_count in divided_dict.items(): star_text = '{}:\t{}'.format(num_stars_field, star_count) num_stars = getattr(stars_dist, num_stars_field) if star_count != num_stars: # レビューの数が元のものと異なると元の星評価の数も異なる star_text = '{} / {}'.format(star_text, num_stars) star_to_text[num_stars_field] = star_text star_texts = [tag('h3', v) for v in star_to_text.values()] return star_texts
def convert(self, map_jsonfile: Union[str, pathlib.Path]) -> str: """商品レビュー内の文を属性と星評価別に対応付けされた JSON ファイルを見やすいように html ファイルに変換 Args: map_jsonfile (Union[str, pathlib.Path]): 対応付けの結果を格納した JSON ファイル Returns: html で記述されたテキスト """ mapped_data = MappingResult.load(map_jsonfile) self.category = mapped_data.category # head コンテンツ product_name = mapped_data.product title = tag('title', product_name) head_content_list = [*self._head_contents, title] head = organize_contents(head_content_list, 'head') # body コンテンツ body = self._make_body_content(mapped_data) # html コンテンツ html_content_list = [head, body] html = organize_contents(html_content_list, 'html') html = DOC_TYPE + '\n' + html return html
def __init__(self, dic_dir: str): self.dic_dir = dic_dir self.__category = '' self._mapper = None js_file = JS_DIR / 'heatmap.js' css_file = CSS_DIR / 'heatmap.css' script_content = read_script(js_file) style_content = read_script(css_file) meta_content = tag('meta', charset='UTF-8') jquery_script_content = tag('script', '', type='text/javascript', src=JQUERY_JS) js_script_content = tag('script', script_content, type='text/JavaScript') css_script_content = tag('style', style_content, type='text/css') self._head_contents = (meta_content, jquery_script_content, js_script_content, css_script_content,)
def _convert_review_data_to_body_content( self, reviewdata: ReviewPageJSON) -> str: """レビューデータを html の body 要素に変換する Args: reviewdata (ReviewPageJSON): Amazon レビューデータ Returns: body タグで囲まれたテキスト """ real_reviews = reviewdata.real_reviews num_reviews_text = 'レビュー数:{}'.format(real_reviews) total_reviews = reviewdata.total_reviews if total_reviews != real_reviews: num_reviews_text = '{} / {}'.format(num_reviews_text, total_reviews) reviews = reviewdata.reviews h3_content_list = _make_stars_texts(reviews, reviewdata.stars_distribution) # <body>コンテンツの用意 body_content_list = [ tag('h1', reviewdata.product), tag('h2', tag('a', '商品レビューページ', href=reviewdata.link)), tag('h2', 'メーカー:{}'.format(reviewdata.maker)), tag('h2', '評価:{}'.format(reviewdata.average_stars)), tag('h2', num_reviews_text), *h3_content_list, ] # <table>コンテンツの用意 table = self._create_table(reviews) # <body>コンテンツの再設定 body_content_list.append(table) body = organize_contents(body_content_list, 'body') return body
def _itemize_text_by_attr_and_star( self, attr_to_star_map: Attr2StarMap, anchor_dict: AnchorDict ) -> Tuple[str, ...]: """属性と星評価別にレビュー文中の文を列挙する Args: attr_to_star_map (Attr2StarMap): 属性と星評価別にレビュー文中の文を格納した辞書 anchor_dict (AnchorDict): 属性と星評価別にレビュー文の情報をまとめたリストとアンカーを格納した辞書 Returns: 列挙された文一覧 """ display_fmt = '{}:{}' enum_content_list = list() for en_attr in attr_to_star_map: attr = self._mapper.en2ja[en_attr] enum_content_list.append(tag('h2', attr, id=en_attr)) for star_str, anchor_prop in anchor_dict[attr].items(): link_id, review_text_info_list = anchor_prop star_disp = STAR_DISPLAY_DICT[star_str] enum_content_list.append( tag('h3', display_fmt.format(attr, star_disp), id=link_id)) if review_text_info_list: li_content_list = list() for review_text_info in review_text_info_list: text = review_text_info.text summary = tag('summary', text) review = normalize(review_text_info.review) marked_text = tag('span', text, class_='sentence-marker') marked_review = review.replace(text, marked_text) details_content_list = [ summary, marked_review.replace('\n', '<br>') ] details = organize_contents(details_content_list, 'details') li_content_list.append(tag('li', details)) ul = organize_contents(li_content_list, 'ul') enum_content_list.append(ul) return tuple(enum_content_list)
def _make_body_content(self, mapped_data: MappingResult) -> str: """html の body タグを作成 Args: mapped_data (MappingResult): 対応付けされた結果 Returns: body タグ """ body_content_list = [] # h1コンテンツ h1_content = tag('h1', mapped_data.product) body_content_list.append(h1_content) # h2コンテンツ review_info_content = [ tag('h2', 'レビュー情報'), tag('h3', tag('a', '商品レビューページ(Amazon)', href=mapped_data.link)), tag('h3', 'メーカー:{}'.format(mapped_data.maker)), tag('h3', '評価:{}'.format(mapped_data.average_stars)), tag('h3', 'レビュー数:{}'.format(mapped_data.total_review)), tag('h3', '総文数:{}'.format(mapped_data.total_text)), ] body_content_list.extend(review_info_content) # heatmapコンテンツ heatmap_content_list = [tag('h2', '属性別での文分布')] attr_to_star_map = mapped_data.mapping heatmap_dict, anchor_dict = self._reorganize_mapped_data(attr_to_star_map) heatmap_table = self._create_heatmap_table(heatmap_dict, anchor_dict) heatmap_content_list.append(heatmap_table) body_content_list.extend(heatmap_content_list) # 属性別のレビュー列挙 enum_content_list = self._itemize_text_by_attr_and_star(attr_to_star_map, anchor_dict) body_content_list.extend(enum_content_list) # bodyコンテンツ body = organize_contents(body_content_list, 'body') return body
def _create_heatmap_table( self, heatmap_dict: HeatmapDict, anchor_dict: AnchorDict ) -> str: """属性と星評価別にレビュー文中の文数をヒートマップにして可視化したものを table タグで作成 Args: heatmap_dict (HeatmapDict): 属性と星評価別にレビュー文中の文数をまとめた辞書 anchor_dict (AnchorDict): 属性と星評価別にレビュー文の情報をまとめたリストとアンカーを格納した辞書 Returns: ヒートマップを実装した table タグ """ tr_content_list = [tag('th', '属性', class_='first')] star_strs = tuple(STAR_DISPLAY_DICT[star_str] for star_str in STAR_CORRESPONDENCE_DICT.values()) tr_content_list.extend([tag('th', star_str) for star_str in star_strs[:-1]]) tr_content_list.append(tag('th', star_strs[-1], class_='last')) tr = organize_contents(tr_content_list, 'tr') thead = tag('thead', tr) jump_fmt = '#{}' tbody_content_list = [] for attr in heatmap_dict: if attr != "その他": attr_anchor = tag('a', attr, href=jump_fmt.format(self._attr_ja2en[attr])) td_header = tag('td', attr_anchor, class_='stats-title') td_content_list = [td_header] num_texts_and_anchor_props = zip(heatmap_dict[attr].values(), anchor_dict[attr].values()) for num_texts, anchor_prop in num_texts_and_anchor_props: anchor = tag('a', num_texts, href=jump_fmt.format(anchor_prop.link_id)) td_content = tag('td', anchor) td_content_list.append(td_content) tr = organize_contents(td_content_list, 'tr', class_='stats-row') tbody_content_list.append(tr) tbody = organize_contents(tbody_content_list, 'tbody') table = organize_contents( [thead, tbody], 'table', class_='heat-map', cellpadding='0', cellspacing='0', border='0', id='heat-map-3') return table
def __init__(self, normalize_mode: bool = False): self.normalize_mode = normalize_mode # スタイルシートを埋め込む css_file = CSS_DIR / 'review_info_style.css' style_content = read_script(css_file, OLD_AND_NEW) # <script>コンテンツの用意 js_file = JS_DIR / 'review_info_tablesorter.js' script_content = read_script(js_file, OLD_AND_NEW) # jsonファイルに依存しない<head>コンテンツの用意 head_content_list = [ tag('meta', charset='UTF-8'), tag('link', rel='stylesheet', id="tablesorter-css", href=TABLESORTER_CSS, type="text/css", media="all"), tag('script', '', type='text/javascript', src=JQUERY_JS), tag('script', '', type='text/javascript', src=TABLESORTER_JS1), tag('script', '', type='text/javascript', src=TABLESORTER_JS2), tag('style', style_content), tag('script', script_content), ] self.head_contents = tuple(head_content_list)
def _make_head_content(self, product_name: str) -> str: """head コンテンツの作成""" title = tag('title', product_name) head_content_list = [*self.head_contents, title] head = organize_contents(head_content_list, 'head') return head
from review_research.htmlgenerator import read_script # 展開すると https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.0/css/theme.metro-dark.min.css?ver=4.9.8 TABLESORTER_CSS = ('https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/' '2.31.0/css/theme.metro-dark.min.css?ver=4.9.8') JQUERY_JS = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js' # 展開すると https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.0/js/jquery.tablesorter.min.js TABLESORTER_JS1 = ('https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/' '2.31.0/js/jquery.tablesorter.min.js') # 展開すると 'https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.0/js/jquery.tablesorter.widgets.min.js' TABLESORTER_JS2 = ('https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/' '2.31.0/js/jquery.tablesorter.widgets.min.js') OLD_AND_NEW = ('TABLE_ID', 'reviewTable') TABLE_DESIGN = 'metro-dark' DOC_TYPE = tag('!DOCTYPE html') # html5の宣言 class ReviewDataConvertor(object): """review.json ファイルの中身を見やすいように html ファイルに変換する Attributes: normalize_mode (bool): レビュー文を正規化する場合は True, そうでない場合は False """ def __init__(self, normalize_mode: bool = False): self.normalize_mode = normalize_mode # スタイルシートを埋め込む css_file = CSS_DIR / 'review_info_style.css' style_content = read_script(css_file, OLD_AND_NEW) # <script>コンテンツの用意
from review_research.htmlgenerator import JS_DIR from review_research.htmlgenerator import CSS_DIR from review_research.nlp import normalize ANCHOR_PROP = ['link_id', 'review_text_info_list'] AnchorProp = namedtuple('AnchorProp', ANCHOR_PROP) STAR_DISPLAY_DICT = {'star1': '★☆☆☆☆', 'star2': '★★☆☆☆', 'star3': '★★★☆☆', 'star4': '★★★★☆', 'star5': '★★★★★'} JQUERY_JS = 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js' DOC_TYPE = tag('!DOCTYPE html') AnchorDict = Dict[str, Dict[str, AnchorProp]] HeatmapDict = Dict[str, Dict[str, int]] Attr2StarMap = Dict[str, Dict[str, Tuple[ReviewTextInfoForMapping, ...]]] class AttrMapConvertor(object): """商品レビュー内の文を属性と星評価別に対応付けされた JSON ファイルを見やすいように html ファイルに変換""" def __init__(self, dic_dir: str): self.dic_dir = dic_dir self.__category = '' self._mapper = None js_file = JS_DIR / 'heatmap.js' css_file = CSS_DIR / 'heatmap.css'