合并多个html文件为一个html文件有很多种方式。如果多个html文件中带有交叉链接,简单将多个文件连接在一起很大概率导致交叉链接失效。本文介绍利用beautifulsoup和re模块合并多个含有交叉链接的html文件,并保证交叉链接的有效性。
本文所介绍的技巧在epub
转换为pdf
文件时经常可以用到。epub
文件中交叉链接的A
标签的href
属性往往指明了html
文件名,id
属性则往往没有带上文件名,例如:
<?xml version="1.0" encoding="utf-8" standalone="no"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-cn"><head>
<title>目 录</title>
<link href="../Styles/style0001.css" rel="stylesheet" type="text/css"/>
</head><body>
<div>
<h2>目 录</h2><br/>
<h4><a href="part0004.xhtml">脂砚斋重评石头记 凡例</a></h4><br/>
<h4><a href="part0005.xhtml">第一回 甄士隐梦幻识通灵 贾雨村风尘怀闺秀</a></h4>
<h4><a href="part0006.xhtml">第二回 贾夫人仙逝扬州城 冷子兴演说荣国府</a></h4>
</div>
</body></html>
可以看到目录中href
分别指向了part0004.xhtml
,part0005.xhtml
,part0006.xhtml
等文件。再如:
<?xml version="1.0" encoding="utf-8" standalone="no"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-cn"><head>
<title>第一回 甄士隐梦幻识通灵 贾雨村风尘怀闺秀</title>
<link href="../Styles/style0001.css" rel="stylesheet" type="text/css"/>
</head><body>
<div>
<h3 class="centered"><span class="kt color_blue">第一回</span></h3>
<h2 class="centered">甄士隐梦幻识通灵 贾雨村风尘怀闺秀</h2><br/>
<p> 列位看官,你道此书从何而来?<a id="navto_2_a" href="part0005.xhtml#lnkback_2_a"><sup>②</sup></a>说起根由虽近荒唐,<span class="small kt red"><img alt="甲戌本" class="font_patch" src="../Images/image00842.gif"/><img alt="侧批" class="font_patch" src="../Images/image00851.gif"/>自占地步。◇自首荒唐,妙!</span>细谙则深有趣味。待在下将此来历注明,方使阅者了然不惑。</p>
<p> <span class="small"><a id="lnkback_1_a" href="part0005.xhtml#navto_1_a">①</a>以上文字见于庚、戚、蒙、列、辰、舒、杨诸本,其中甲辰本为回前批,馀本均为正文。此段与甲戌本凡例第五条略同,玩其文意应非正文,现作为回前批处理。</span></p>
<p> <span class="small"><a id="lnkback_2_a" href="part0005.xhtml#navto_2_a">②</a>底本正文从此开始。按:本书第一至八回、第十三至十六回、第二十五至二十八回以甲戌本为底本,第六十四回、第六十七回以列藏本为底本,其馀部分以庚辰本为底本,下文所称底本均以此为据,不另注。</span></p>
</div>
</body></html>
其中<a id="navto_2_a" href="part0005.xhtml#lnkback_2_a"><sup>②</sup></a>
中的a
标签,id
属性不包含文件名信息,可能与其他文件中的a
标签的id
重复,href
标签则包含文件名信息“part0005.xhtml”
,合并为pdf
文件后,文件“part0005.xhtml”
不再存在,该链接失效。
为了避免上述问题,可以考虑将epub
中所有网页文件合并为一个网页文件,a
标签的id
属性添加文件名信息改成形如“part0004_lnkback_2_a”
的形式以避免出现重复id
,href
属性则改成形如“#part0004_lnkback_2_a”
的形式直接链接到本文件中的锚点。
要实现上述目标,可以利用BeautifulSoup
读入相关文件进行操作。示例代码如下:
import copy,re
from bs4 import BeautifulSoup
# merg_html保存所有网页文件合并后的html
merg_html = BeautifulSoup()
# 这里示例合并两个html字符串,实际操作时应该指定网页文件路径并遍历全部网页文件
for i in range(2): # 用遍历字符串模拟遍历网页文件
if i==0:
soup = BeautifulSoup(html1, 'lxml')
else:
soup = BeautifulSoup(html2, 'lxml')
#修改注释部分链接属性,查找既有id属性又有href属性的a标签
a_s=soup.find_all(name='a',attrs={'id':True,'href':True})
# 这里根据给出html字符串的特征进行处理
if len(a_s)>0:
for a in a_s:
f_name = a['href'].split('.')[0] # 从href属性中取得主文件名
anchor_name = a['href'].split('#')[-1] # 取得链接目标的锚点名
a['id'] = f_name + '_' + a['id'] # 将id前面加上主文件名
a['href'] = '#' + f_name + '_' + anchor_name # 修改链接目标
#修改目录部分链接属性
a_s=soup.find_all(name='a',attrs={'id':False,'href':True})
if len(a_s)>0:
for a in a_s:
f_name = a['href'].split('.')[0]
a['id'] = 'nav_' + f_name
a['href'] = '#' + f_name
#回目编号处插入链接,title取得回目编号所在标签,从html2中可以看出特征
title = soup.find(name='span',text=re.compile('^第[一二三四五六七八九十]{1,5}回$'))
if title:
s = 'part0005' # 这里用一个固定字符串示例,实际操作中应该在遍历文件时取得主文件名
a = soup.new_tag('a')
a['id'] = s
a['href'] = '#nav_{}'.format(s)
a.string = title.text.strip()
title.string = '' # 清除回目编号中原有内容
title.append(a)
#将后续html文件字符串中body标签的内容全部复制到第一个文件中
if not merg_html.body:
merg_html = copy.copy(soup)
else:
merg_html.body.append(copy.copy(soup.body))
data = merg_html.prettify()
# 删除字符串中多余的<body>标签和</body>标签,用[\s\S]代表所有字符,re.M指定多行模式
data = re.sub('</body>[\s\S]*?<body[\s\S]*?>','',data,flags=re.M)
data = re.sub('</body>[\s\S]*?</body[\s\S]*?>','</body>',data,flags=re.M)
print(data)
上述代码中的html1
和html2
分别是本文开头的两个html
文件内容。运行后合并html
字符串内容如下:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xml:lang="zh-cn" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>
目 录
</title>
<link href="../Styles/style0001.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div>
<h2>
目 录
</h2>
<br/>
<h4>
<a href="#part0004" id="nav_part0004">
脂砚斋重评石头记 凡例
</a>
</h4>
<br/>
<h4>
<a href="#part0005" id="nav_part0005">
第一回 甄士隐梦幻识通灵 贾雨村风尘怀闺秀
</a>
</h4>
<h4>
<a href="#part0006" id="nav_part0006">
第二回 贾夫人仙逝扬州城 冷子兴演说荣国府
</a>
</h4>
</div>
<div>
<div>
<h3 class="centered">
<span class="kt color_blue">
<a href="#nav_part0005" id="part0005">
第一回
</a>
</span>
</h3>
<h2 class="centered"> 甄士隐梦幻识通灵 贾雨村风尘怀闺秀 </h2>
<br/>
<p> 列位看官,你道此书从何而来?
<a href="#part0005_lnkback_2_a" id="part0005_navto_2_a">
<sup> ② </sup> </a> 说起根由虽近荒唐, <span class="small kt red">
<img alt="甲戌本" class="font_patch" src="../Images/image00842.gif"/>
<img alt="侧批" class="font_patch" src="../Images/image00851.gif"/>
自占地步。◇自首荒唐,妙! </span> 细谙则深有趣味。待在下将此来历注明,方使阅者了然不惑。 </p>
<p> <span class="small">
<a href="#part0005_navto_1_a" id="part0005_lnkback_1_a"> ① </a>
以上文字见于庚、戚、蒙、列、辰、舒、杨诸本,其中甲辰本为回前批,馀本均为正文。此段与甲戌本凡例第五条略同,玩其文 意应非正文,现作为回前批处理。
</span>
</p>
<p>
<span class="small">
<a href="#part0005_navto_2_a" id="part0005_lnkback_2_a">
②
</a>
底本正文从此开始。按:本书第一至八回、第十三至十六回、第二十五至二十八回以甲戌本为底本,第六十四回、第六十七回以 列藏本为底本,其馀部分以庚辰本为底本,下文所称底本均以此为据,不另注。
</span>
</p>
</div>
</div>
</body>
</html>
可以看到两个html
文件字符串已经合并成一个单一的html
文件字符串,且相关链接已修正: