
「Pythonに興味がある」「プログラミングを勉強してみたい」そんな方におすすめの本連載。
今回は、Excelにまとめたリンク集をPythonでHTML化し、"見やすいポータル風"リンク集を作ります!日々の作業の中で発生する小さな手間を、減らしてくれる便利ツールです。
手順の紹介だけではなく、読むだけでも「なるほど!」と思っていただけるよう、ポイントもやさしく解説しています。
今回つくるもの
◆Excel×Python×HTMLで見やすく!自分だけの社内ポータル風リンク集
業務に必要なWebサイトやシステムのURLリンクは、散らばりやすく「探すムダ」を生みがちです。自分用にExcelでリンク集を作っている方も多いですが、ただURLを並べただけでは、必要な情報を探すのに手間がかかります。
そこで本記事では、いつものExcelデータをPythonでHTML化し、すっきり見やすいポータル風のリンク集に変換する方法を紹介します。データの更新は使い慣れたExcelで行い、使うときはブラウザで快適にアクセス。さらに、生成AIの力を借りればHTMLも、自分好みにカスタマイズ可能。日々の小さなタイムロスをなくし、デスクワークを圧倒的に効率化しましょう!
- リンクが散らばる職場で起きる"探すムダ"
- Pythonプログラミングの準備
・Pythonプログラミング環境の構築
・必要なライブラリのインストール - サンプルのExcelファイルとHTMLファイルについて
- Python×Excelで実践!
・import文
・目次を作成する関数
・リンクを作成する関数
・カテゴリーを作成する関数
・Excelデータをカテゴリーごとに整理してHTMLへ差し込む関数
・HTMLファイルを書き出す関数
メイン実行文を書いてコードを実行してみよう - 全体のコードはこちらから
1.リンクが散らばる職場で起きる"探すムダ"
お仕事で使う"URLリンク"は、思っている以上にあちこちへ散らばりがちです。
勤怠システム、経費申請、社内Wiki、共有フォルダ、マニュアル…と業務に必要なページはたくさんあります。それらを使うたびに検索したり、過去のログを見返したりしている方も多いのでは?1回ごとは些細ですが、毎日のように繰り返すと意外に大きな“探すムダ”になっていきます。
改善策として、自分のリンク集をExcelに作っている方もいるかもしれません。ただExcelにURLとタイトルを貼り付けただけでは、必要なリンクを探すたびに一覧を目で追うことになりますし、見た目も含めて、より使いやすくしたいところです。

そこで、本記事では自分用に作成したExcelリンク集をPythonでHTML化し、"見やすいポータル風"リンク集に変換する方法を紹介します。
データ管理はExcelで続けながら、見た目はHTMLでカスタマイズして、使うときはブラウザで快適に見ることができる。"探すムダ"をなくす、お仕事の効率化を実現しましょう!
2. Pythonプログラミングの準備
■Pythonプログラミング環境の構築
Pythonプログラミングの環境は「最速でPython環境を構築してプログラミングをはじめよう」を参照してください。リンク先にはPythonのインストール方法、プログラムの記述・実行方法、外部ライブラリのインストール方法をまとめています。
■必要なライブラリのインストール
以下の3つの外部ライブラリをインストールしておきましょう。
(1)Pandas
PandasはPythonでExcelを扱うときに便利な外部ライブラリです。次のコマンドでインストールしましょう。
Pandasをインストールすると、依存ライブラリであるNumPyもインストールされます。 pip install pandas
(2)openpyxl
openpyxlはPandasでExcelを読み込むときに必要です。以下のコマンドでインストールしましょう。pip install openpyxl
(3)Beautiful Soup 4
Beautiful Soup 4はPythonでHTMLを整えたり、解析したりするときに便利な外部ライブラリです。今回のようにHTMLを扱う場合にも役立ちます。次のコマンドでインストールしましょう。pip install beautifulsoup4
外部ライブラリのインストールが終了したら、以下のコマンドを入力して、JupyterLabを立ち上げましょう。立ち上がったら、Notebookと表示されている下にあるPython 3のアイコンをクリックします。

3. サンプルのExcelファイルとHTMLファイルについて
本記事はリンク集のデータベースであるExcelファイルを使います。Excelファイルはここからダウンロードしてください。
▼クリックするとファイルがダウンロードされます。
https://www.r-staffing.co.jp/rasisa/wp-content/uploads/2026/05/links_sample.xlsx
ファイルには「カテゴリー」「名称」「URL」「メモ」の4つを任意行数入力してください。ファイルはプログラム実行フォルダの直下に置きましょう。
※サンプルのExcelファイルにはダミーのURLが書かれています。

また、本記事ではリンク集をHTMLに変換しますが、変換にはテンプレートとして既に作成してあるHTMLファイルを使います。Pythonで読み込み、Excelに記載されたデータに応じてHTMLファイルを編集するため、別途HTMLに関する知識は不要です。
テンプレートHTMLファイルはここからダウンロードしてください。
▼クリックするとHTMLファイルがブラウザで展開されます。
Google Chromeの場合、サイト上で右クリック→[別名で保存...]でHTMLファイルを保存できます。
https://www.r-staffing.co.jp/rasisa/wp-content/uploads/2026/05/portal_template.html
このテンプレートHTMLファイルには、見栄えの設定の他にも既にカテゴリーにまとめる書式やリンク、説明書きの見本が書かれています。

テンプレートHTMLファイルをブラウザで開くと、次のような画面が表示されます。

テンプレートHTMLファイルもプログラム実行フォルダの直下に置いておきましょう。
4. Python×Excelで実践!
プログラミングの方針としては、まずは必要な関数を作成して、その後にそれらを組み合わせてメインの処理を完成させます。コード内のコメントを含んでも全部で130行程度と比較的シンプルな構成にしています。
■import文
ここからはPythonで作っていきます。
最初はコードの先頭に各種import文を書きます。
from pathlib import Path
import copy
import pandas as pd
from bs4 import BeautifulSoup
copycopyはPython標準ライブラリの1つで、オブジェクトをコピーするときに使用します。今回はHTMLテンプレートを複製するために利用します。■目次を作成する関数
まずは1つ目の関数create_toc_itemを作成しましょう。
略
from bs4 import BeautifulSoup
def create_toc_item(toc_item_template, section_id, category): # ここから追加
"""目次のliタグを作成"""
toc_item = copy.deepcopy(toc_item_template)
toc_item.attrs.pop("id", None)
toc_item.attrs.pop("data-template", None)
toc_anchor = toc_item.find("a")
toc_anchor["href"] = f"#{section_id}"
toc_anchor.clear()
toc_anchor.append(category)
return toc_item # ここまで追加
この関数の引数に設定されている
toc_item_templateは目次用としてあらかじめHTML内に用意しておいたliタグです。 そのliタグの中身を、クリックしたときにジャンプする先の
section_id、カテゴリー名categoryの引数で書き換えることでHTMLを生成します。ここで
copy.deepcopyとは、元のテンプレートはそのまま残しつつ、新しい目次項目だけを作るためにコピーする処理です。同じidが複数できてしまったり、不要な属性がHTML内に残ったりするため
.popで元々のテンプレートに記載してあった情報を削除しています。 .findでHTMLのリンク用タグであるaタグを検索し設定を行い、最後にreturnで目次を返します。■リンクを作成する関数
続いてリンク一覧を作成するためのcreate_link_item関数を追加しましょう。
略
return toc_item
def create_link_item(link_item_template, item): # ここから追加
"""リンク一覧のliタグを作成"""
link_item = copy.deepcopy(link_item_template)
link_item.attrs.pop("id", None)
link_item.attrs.pop("data-template", None)
link_tag = link_item.select_one(".link-name") or link_item.find("a")
link_tag["href"] = str(item["URL"])
link_tag["target"] = "_blank"
link_tag["rel"] = "noopener noreferrer"
link_tag.clear()
link_tag.append(str(item["名称"]))
memo_text = str(item["メモ"])
memo_tag = link_item.select_one(".link-memo")
if memo_tag is not None:
if memo_text:
memo_tag.string = memo_text
else:
memo_tag.decompose()
return link_item # ここまで追加
link_item_templateと、URL・名称・メモといった情報を持つitemを受け取り、リンクに記載する情報link_itemを返す関数です。 .deepcopyや.pop、.findからの処理は先ほどと同じです。 link_tag["target"] = "_blank"はリンク先を新しいタブで開くように設定しています。ifではメモ欄があれば書き込み、なければ削除するといった処理をしています。memo_tag.decompose()はタグそのものを削除するときに便利です。■カテゴリーを作成する関数
次はカテゴリーを作成するcreate_category_section関数を追加しましょう。
略
return link_item
def create_category_section(section_template, link_item_template, group): # ここから追加
"""カテゴリーのセクションタグを作成"""
section = copy.deepcopy(section_template)
section.attrs.pop("id", None)
section.attrs.pop("data-template", None)
section["id"] = group["id"]
category_title = section.select_one(".category-title") or section.find("h2")
category_title.string = group["category"]
ol = section.select_one(".links") or section.find("ol")
ol.clear()
for item in group["items"]:
ol.append(create_link_item(link_item_template, item))
return section # ここまで追加
section_templateをもとに、カテゴリー1つ分のセクションsectionを作るための関数です。copy.deepcopyや attrs.popの役割はこれまでと同じです。section["id"] = group["id"]ではセクションにIDを設定し、目次からジャンプできるようにしています。category_title.string = group["category"]では見出しをカテゴリー名に差し替えます。 また、
ol.clear()でテンプレート内の仮のリンクを空にしたあと、for文の中でcreate_link_item(link_item_template, item)を使ってリンク項目を1件ずつ追加しています。■Excelデータをカテゴリーごとに整理してHTMLへ差し込む関数
続いて、Excelデータの整理をする関数inject_portal_contentを追加しましょう。
略
return section
def inject_portal_content(soup, elements, dataframe): # ここから追加
"""テンプレートに目次とカテゴリー別リンクを差し込む"""
groups = []
for index, (category, frame) in enumerate(dataframe.groupby("カテゴリー", sort=False), start=1):
groups.append(
{
"id": f"cat-{index}",
"category": str(category),
"items": frame.to_dict(orient="records"),
}
)
elements["portal_title"].string = DEFAULT_PAGE_TITLE
elements["toc_list"].clear()
elements["category_sections"].clear()
toc_item_template = elements["toc_item_template"]
section_template = elements["section_template"]
link_item_template = elements["link_item_template"]
for group in groups:
elements["toc_list"].append(create_toc_item(toc_item_template, group["id"], group["category"]))
elements["category_sections"].append(create_category_section(section_template, link_item_template, group))
return soup # ここまで追加
dataframeをカテゴリーごとに整理し、テンプレートHTMLの中へ目次とリンク一覧をまとめて差し込むための関数です。 前半のfor文では、
dataframe.groupby("カテゴリー", sort=False) を使ってデータをカテゴリーごとに分け、カテゴリー名・ID・リンク一覧を持つgroupsを作成しています。frame.to_dict(orient="records")によって、各行を辞書のリストに変換しているのがポイントです。elements["portal_title"].string = DEFAULT_PAGE_TITLEではページタイトルを書き換え、clear()で目次とカテゴリー一覧の中身をいったん空にしています。その後、create_toc_item()で目次項目を、create_category_section()でカテゴリーごとのセクションを作成し、それぞれテンプレートHTMLへ追加しています。つまりこの関数は、Excelデータ全体をカテゴリーごとにまとめ直し、ページタイトル・目次・カテゴリー一覧を一気に組み立てる役割を持っています。■HTMLファイルを書き出す関数
ExcelファイルとテンプレートHTMLファイルを読み込み、最終的なポータルHTMLを生成して保存するための関数build_portalを作成して関数群の準備は終了です。
略
return soup
def build_portal(excel_path, template_path, output_path): # ここから追加
"""Excelとテンプレートから最終HTMLを生成する"""
dataframe = pd.read_excel(excel_path, sheet_name=0)
template_text = Path(template_path).read_text(encoding="utf-8")
soup = BeautifulSoup(template_text, "html.parser")
elements = {
"portal_title": soup.find(id="portal-title"),
"toc_list": soup.find(id="toc-list"),
"category_sections": soup.find(id="category-sections"),
"toc_item_template": soup.find(id="toc-item-template"),
"section_template": soup.find(id="section-template"),
"link_item_template": soup.find(id="link-item-template"),
}
soup = inject_portal_content(soup, elements, dataframe)
destination = Path(output_path)
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(str(soup), encoding="utf-8")
return destination # ここまで追加
pd.read_excel(excel_path, sheet_name=0)でExcelの先頭シートを読み込み、Path(template_path).read_text()でテンプレートHTMLを文字列として取得しています。さらにBeautifulSoup(template_text, "html.parser")でHTMLを解析し、あとで書き換えたい要素をelementsにまとめています。その後、inject_portal_content(soup, elements, dataframe)を呼び出して、ページタイトル・目次・カテゴリー一覧などの中身をまとめて差し込みます。最後に、
destination.write_text(str(soup), encoding="utf-8")で完成したHTMLを書き出し、出力先のパスdestinationを返しています。つまりこの関数は、Excelの読み込みからHTML生成、保存までをまとめて実行するメイン処理です。
■メイン実行文を書いてコードを実行してみよう
最後に、関数群の下にメイン実行文を書いてプログラムは完成です。
略
return destination
# ファイルパスとページタイトル # ここから追加
DEFAULT_EXCEL_PATH = "links_sample.xlsx"
DEFAULT_TEMPLATE_PATH = "portal_template.html"
DEFAULT_OUTPUT_PATH = "portal_links.html"
DEFAULT_PAGE_TITLE = "LINK PORTAL"
# HTMLファイル生成
output_file = build_portal(
excel_path=DEFAULT_EXCEL_PATH,
template_path=DEFAULT_TEMPLATE_PATH,
output_path=DEFAULT_OUTPUT_PATH,
) # ここまで追加
コードを実行すると、プログラム実行フォルダにportal_links.htmlというHTMLファイルが作成されます。
このファイルを開くといつも使っているブラウザが立ち上がり、次のようなページが表示されます。

「カテゴリー目次」というところに自分で設定したカテゴリーに対応するボタンがあります。クリックすると該当のリンク一覧へジャンプするので、どんなにたくさんのリンクがあっても素早く目的の項目を探すことができます。
5. 全体のコードはこちらから
この記事では、Excelで管理しているリンク集をPythonでHTML化し、見やすい社内ポータルのようなページに変換する方法を紹介しました。
Excelはデータ管理に向いており、HTMLは見た目や使いやすさの調整に向いています。この2つを役割分担させるだけでも、日々の“探すムダ”を大きく減らせるはずです。
さらに面白いのは、このテンプレートHTMLファイル自体も自由に育てていけることです。たとえば生成AIに対して「もっとモダンなデザインにして」「ダークモードっぽい配色にして」「カード型でもっと見やすくして」などと依頼すれば、HTMLやCSSの知識があまりなくても、テンプレートを少しずつ好みに近づけていけます。
つまり、リンクの中身はExcelで管理し、見た目はPythonと生成AIの力を借りて整える、という分担もできるわけです。最初はシンプルなテンプレートから始めて、慣れてきたら色やレイアウト、アイコン、検索機能などを足していくのもよいでしょう。自分にとって使いやすいポータルへ育てていくこと自体が、Pythonの活用の次の一歩になります。
最後に、この記事で紹介した全体のコードを載せます。パソコンで開ける方はぜひコピペして遊んでみてください。
from pathlib import Path
import copy
import pandas as pd
from bs4 import BeautifulSoup
def create_toc_item(toc_item_template, section_id, category):
"""目次のliタグを作成"""
toc_item = copy.deepcopy(toc_item_template)
toc_item.attrs.pop("id", None)
toc_item.attrs.pop("data-template", None)
toc_anchor = toc_item.find("a")
toc_anchor["href"] = f"#{section_id}"
toc_anchor.clear()
toc_anchor.append(category)
return toc_item
def create_link_item(link_item_template, item):
"""リンク一覧のliタグを作成"""
link_item = copy.deepcopy(link_item_template)
link_item.attrs.pop("id", None)
link_item.attrs.pop("data-template", None)
link_tag = link_item.select_one(".link-name") or link_item.find("a")
link_tag["href"] = str(item["URL"])
link_tag["target"] = "_blank"
link_tag["rel"] = "noopener noreferrer"
link_tag.clear()
link_tag.append(str(item["名称"]))
memo_text = str(item["メモ"])
memo_tag = link_item.select_one(".link-memo")
if memo_tag is not None:
if memo_text:
memo_tag.string = memo_text
else:
memo_tag.decompose()
return link_item
def create_category_section(section_template, link_item_template, group):
"""カテゴリーのセクションタグを作成"""
section = copy.deepcopy(section_template)
section.attrs.pop("id", None)
section.attrs.pop("data-template", None)
section["id"] = group["id"]
category_title = section.select_one(".category-title") or section.find("h2")
category_title.string = group["category"]
ol = section.select_one(".links") or section.find("ol")
ol.clear()
for item in group["items"]:
ol.append(create_link_item(link_item_template, item))
return section
def inject_portal_content(soup, elements, dataframe):
"""テンプレートに目次とカテゴリー別リンクを差し込む"""
groups = []
for index, (category, frame) in enumerate(dataframe.groupby("カテゴリー", sort=False), start=1):
groups.append(
{
"id": f"cat-{index}",
"category": str(category),
"items": frame.to_dict(orient="records"),
}
)
elements["portal_title"].string = DEFAULT_PAGE_TITLE
elements["toc_list"].clear()
elements["category_sections"].clear()
toc_item_template = elements["toc_item_template"]
section_template = elements["section_template"]
link_item_template = elements["link_item_template"]
for group in groups:
elements["toc_list"].append(create_toc_item(toc_item_template, group["id"], group["category"]))
elements["category_sections"].append(create_category_section(section_template, link_item_template, group))
return soup
def build_portal(excel_path, template_path, output_path):
"""Excelとテンプレートから最終HTMLを生成する"""
dataframe = pd.read_excel(excel_path, sheet_name=0)
template_text = Path(template_path).read_text(encoding="utf-8")
soup = BeautifulSoup(template_text, "html.parser")
elements = {
"portal_title": soup.find(id="portal-title"),
"toc_list": soup.find(id="toc-list"),
"category_sections": soup.find(id="category-sections"),
"toc_item_template": soup.find(id="toc-item-template"),
"section_template": soup.find(id="section-template"),
"link_item_template": soup.find(id="link-item-template"),
}
soup = inject_portal_content(soup, elements, dataframe)
destination = Path(output_path)
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_text(str(soup), encoding="utf-8")
return destination
# ファイルパスとページタイトル
DEFAULT_EXCEL_PATH = "links_sample.xlsx"
DEFAULT_TEMPLATE_PATH = "portal_template.html"
DEFAULT_OUTPUT_PATH = "portal_links.html"
DEFAULT_PAGE_TITLE = "LINK PORTAL"
# HTMLファイル生成
output_file = build_portal(
excel_path=DEFAULT_EXCEL_PATH,
template_path=DEFAULT_TEMPLATE_PATH,
output_path=DEFAULT_OUTPUT_PATH,
)
メーカー勤務機械系エンジニア。WATLABブログ運営者。工学計算に関する知識の習得を目指し、Pythonの学習を2019年からはじめる。仕事以外にも、趣味のプログラミングやPythonコミュニティへの参加を行っている。また、月間数万PVのPythonブログ「WATLAB」を立ち上げ、初心者向けに図を多くしたわかりやすい記事を作成・公開している。著書は『いきなりプログラミング Python』(翔泳社)。
ブログ:https://watlab-blog.com/
※本記事に記載されている会社名、製品名はそれぞれ各社の商標および登録商標です。
※本稿に記載されている情報は2026年4月時点のものです。

