整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

牛逼!40行Python代码把html网页保存为pdf,太方便了

近临近开学了,大家都在忙着准备各种学习的资料,准备在新的学期好好学习,充实自己。小编身边的同学也是如此,最近,小编的同学小丽就遇到了一个很棘手的问题。

她想将一个网页的Python学习的教程打印下来,方便自己来学习,但是上千页的教程,如果通过手动的方式,一个一个的去转成pdf并保存到本地,实在是麻烦的不。

这就是一个html转pdf的问题,其实网上有很多不错的html资源,但是苦于学习起来,不方便!于是小编就跟小丽保证,这点小事包在我身上。今天,小编就跟分享一下如何用Python把html资料变成pdf。

01.抓取的学习资料

如今网上的在线学习资料可谓是多如牛毛,为了方便讲解,小编就利用python3.9.2的中文文档作为演示的例子,来将其抓取并保存到本地,其网页链接如下:

https://docs.python.org/zh-cn/3.9/tutorial/index.html

打开上述链接后,大家会在网页中找到不同内容的链接地址,包括了基础的python字符、python语法等内容。

02.获取网页链接

在上图中,我们需要格外关注的是红色方格标注的链接,每个链接都会跳转到对应的子网页中,而在子网页中,就是我们想要保存的内容。

可以看到,上图中,在python速览子页面中,包含了我们需要提取的文字内容。所以将html内容保存为pdf的第一步便是获取到子页面的链接。由于教程大都是固定内容,因此对于教程的网页,大都采用的是静态页面,在网页源代码中可以很轻松地找到子页面的网页链接。

对于子网页的链接抓取,程序如下图所示:

程序中,通过BeautifulSoup库来解析网页源代码,然后提取所有的子页面链接地址并返回,如果抓取失败,则直接返回None


03.html转pdf

在得到子网页的链接后,接下来就是将html的子网页保存为pdf文件。小编使用的pdfkit库,pdfkit库可以将网页保存为pdf文档。首先小编来介绍一下pdfkit库的安装。

  • 下载https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox-0.12.6-1.mxe-cross-win64.7z 并解压到本地文件中。(后台输入:pdf) 直接获取。
  • 将解压文件中的bin文件路径添加到系统变量Path中。
  • 执行pip install pdfkit
  • 执行pip install wkhtmltopdf

按照上述的操作流程,就可以安装pdfkit库。对于pdfkit库的使用,常见的用法有以下三种:

上面的程序主要完成以下几步:

首先需要指定wkhtmltopdf.exe文件的路径;

  • 然后分别通过from_url、from_file和from_string的三种方式来保存为pdf文件;
  • 需要注意的是,from_file和from_url中的第一个参数必须是一个html的字符串或者是html文档的列表;
  • 但是小编通过程序运行发现,from_url第一个参数只能是html的字符串,不能是html的列表


因此,pdfkit库只能将子网页保存为单独的pdf文档,无法直接通过pdfkit库将所有的子网页拼接成一个完整的pdf文档,小编通过PyPDF2库中的PdfFileMerger类来实现pdf文档的拼接。程序如下图所示。

程序中首先将所有的html网页保存为单独的pdf文档,然后通过PdfFileMerger类对象来实现pdf文档的拼接。最后就可以得到全部的pdf内容。最后我们通过视频的展示,来看一下程序的效果吧。


除此之外,程序不光可以抓取python3.9的中文文档,针对其他的在线文档,只需要对获取网页链接的程序进行修改即可抓取,例如对于Flask中文文档的抓取,程序只需要按照下图进行修改,即可将Flask的在线文档保存为PDF文档。

04.总结

学习Python其实非常有趣,也很有用。因为Python有大量的现成的库,可以帮助我们把工作中的很多琐碎的烦事轻松解决。小编将上述的程序稍加修改,很快就帮阿丽搞定了教程,保存为pdf发送给了她,小编与女神的关系更拉近了一步

请耐心慢慢刷新到页面完整显示出来

https://fjxasdf.github.io/daogou/

原文章https://www.toutiao.com/i6711294610594857476/

哪些优化?

1.显示商品图片

2.没有显示全部商品,只显示热门商品,避免数据、图片加载太慢

3.样式稍微美化了

4.上传到GitHub,不过GitHub很卡,要刷新等好久才能看到

python读取excel生成json代码

import xlrd
from datetime import date,datetime
import json
file = '精选优质商品清单(内含优惠券).xls'
def read_excel():
 wb = xlrd.open_workbook(filename=file)#打开文件
 # print(wb.sheet_names())#获取所有表格名字
 sheet1 = wb.sheet_by_index(0)#通过索引获取表格
 # sheet2 = wb.sheet_by_name('Page1')#通过名字获取表格
 # print(sheet1)
 # print(sheet2)
 # print(sheet1.name) #表 名
 rows = sheet1.nrows #多少行
 # print(sheet1.ncols) #多少列
 # rows = sheet1.row_values(1)#获取行内容
 category0 = sheet1.col_values(4)#获取列内容(类目)
 del category0[0]
 category = sorted(set(category0),key=category0.index) #类目列表->去重
 data = []
 data_hot = []
 # print(rows)
 # print(cols)
 for i,v in enumerate(category):
 category[i] = v.replace("/", "、")#吧"/"替换"、"
 data.append([category[i],[]])
 jsonData = json.dumps(category, ensure_ascii=False)
 with open('./daogou/category.json', 'w',encoding="utf-8") as f:
 f.write(jsonData)#保存分类json
 for i,v in enumerate(data):
 for x in range(rows):
 if v[0] == (sheet1.cell(x,4).value.replace("/", "、")):
 xo = sheet1.row_values(x)
 y = [xo[1],xo[2],xo[4],xo[6],xo[21]] #选择保存商品名称、图片、链接等
 data[i][1].append(y)
 if x>0 and float(sheet1.row_values(x)[9]) >= 1: #选择佣金多的作为热门
 xo = sheet1.row_values(x)
 y = [xo[1],xo[2],xo[4],xo[6],xo[21]]
 data_hot.append(y)
 data_hot = data_hot[:24]
 jsonData_hot = json.dumps(data_hot, ensure_ascii=False)
 with open('./daogou/results_hot.json', 'w',encoding="utf-8") as f:
 f.write(jsonData_hot)#保存热门商品json
 for i,v in enumerate(data):
 jsonData = json.dumps(v[1], ensure_ascii=False)
 with open('./daogou/'+v[0]+'.json', 'w',encoding="utf-8") as f:
 f.write(jsonData)#保存每个分类商品json
 # jsonData1 = json.dumps(data, ensure_ascii=False)
 # with open('results.json', 'w',encoding="utf-8") as f:
 # f.write(jsonData1)
 # print(sheet1.cell(0,0).value)#获取表格里的内容,第一行第一个格内容
 # print(sheet1.cell_value(0,0))#获取表格里的内容,第一行第一个格内容
 #print(sheet1.row(0)[0].value)#获取表格里的内容,第一行第一个格内容
if __name__ == '__main__':
 	read_excel()

创建html页面,并引用json文件

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<script src="../js/jquery.min.js"></script>
	<link rel="stylesheet" href="../font/Alibaba-PuHuiTi-Regular.css">
		<script>
		var navo = '';
var info = '';
var data_total;//总数据
function color16(){//十六进制颜色随机
	var r = Math.floor(Math.random()*256);
	var g = Math.floor(Math.random()*256);
	var b = Math.floor(Math.random()*256);
	var color = '#'+r.toString(16)+g.toString(16)+b.toString(16);
	return color;
}
function nav_href(title,id){//锚跳转
		if ($('#'+title).length>0) {
		location.href="#"+title;
		}else{
			var div_title = "<div id='"+title+"' style='float:left;'>";
			var div_content = "<div class='pro_img' style='background:#ff5000'><b style='color:#FFF'>"+title+"</b><\/div>";
			$('#load').show();
			$.get('./'+title+'.json', function(data) {
				$.each(data, function(index, val) {
					div_content+="<a style='background:"+color16()+"' target='_blank' href='"+val[4]+"' class='pro_img'><img onload='$(this).show()' src="+val[1]+" style='width:100%;height:100%;display:none;' />"+val[0]+"<span class='money'>¥"+val[3]+"</span><span class='title'>"+val[0].substring(0,25)+"...</span><\/a>";
					if ((index+1)==data.length) {
						var div_footer ="</div><br>";
						infoo=div_title+div_content+div_footer;
						$('#content').append(infoo);
						location.href="#"+title;
						$('#load').hide();
					}
						 });
			},'json');
			
						 
			
		}
		
	
}
function get_data(){
	$.get('./category.json', function(data) {//导航
				$.each(data, function(index, val) {
					 // console.log(val[0]);
					 navo+="<a class='nav' href=javascript:;nav_href(\'"+val+"\',"+index+")>"+val+"</a> ";
				});
	$('#nav').html(navo);
	$.get('./results_hot.json', function(data) {//热门商品
					var div_title = "<div id='热门商品' style='float:left;'>";
					 var div_content = "<div class='pro_img' style='background:#ff5000'><b style='color:#FFF'>热门商品</b></span><\/div>";
		$.each(data, function(index, val) {
					 // console.log(val);
					 
					 	 div_content+="<a style='background:"+color16()+"' target='_blank' href='"+val[4]+"' class='pro_img'><img onload='$(this).show()' src="+val[1]+" style='width:100%;height:100%;display:none;' />"+val[0]+"<span class='money'>¥"+val[3]+"</span><span class='title'>"+val[0].substring(0,25)+"...</span><\/a>"
					 
					 
					 // if (index==3) {return false}
				});
		var div_footer ="</div><br>";
		info+=div_title+div_content+div_footer;
	$('#content').html(info);
	$('#load').hide(0);	
	});
			},'json');
}
	
		
	</script>
	<style>
	#body{
		/*border: 1px solid #eee;*/
		width: 1110px;
		margin:0 auto;
	}
	a.nav{
		text-decoration: none;
		margin-right: 10px;
		padding: 0 5px;
		/*padding-bottom: 10px;*/
		display: inline-block;
		color: #000;
 font-weight: 700;
	}
	#nav{
		display: inline-block;
	}
*,html,body{
	font-family:"Alibaba-PuHuiTi-Regular";
}
		#content{
			margin-top: 10px;
			display: inline-block;
		}
		.money{
	 width: 100%;
 /* padding: 0px 10px; */
 background: #ffffffcc;
 position: absolute;
 left: 0;
 bottom: 50px;
 height: 30px;
 line-height: 30px;
 color: #F40;
 font-weight: 700;
 /* border-radius: 0 8px 0 0; */
 text-align: left;
 font-size: 18px;
		}
				.title{
 width: 100%;
 font-weight: 700;
 /* padding: 0px 10px; */
 background: #ffffffcc;
 position: absolute;
 left: 0;
 bottom: 0;
 height: 50px;
 line-height: normal;
 color: #000;
 /* border-radius: 0 8px 0 0; */
 text-align: left;
 font-size: 15px;
		}
		.pro_img{
			position: relative;
			float: left;
			width: 220px;
			height: 220px;
			line-height: 220px;
			text-align: center;
			border: 1px solid #eee;
			cursor: pointer;
			font-size: 30px;
			/*white-space:normal; */
			overflow:hidden; /*超过部分不显示*/
      text-overflow:ellipsis; /*超过部分用点点表示*/
 /*     white-space:nowrap;/*不换行*/
		}
		.input1{
			 width: 300px;
			 height: 30px;
			 border: 1px solid #888;
				border-radius: 10px 0 0 10px;
				outline-style: none ;
	
		}
		.button1{
			margin-left: -7px;
 width: 50px;
 height: 34px;
 border-radius:0 10px 10px 0 ;
 outline-style: none ;
		}
		#search1{
			/*width: 350px;*/
			/*margin:0 auto;*/
		}
		#load{
			position: fixed;
			width: 100%;
			height: 100%;
			top: 0;
			left: 0;
			background: #000000ad;
			z-index: 999;
			text-align: center;
			vertical-align: middle;
		}
		#load>span{
 display: inline-block;
 vertical-align: middle;
 height: 100%;
		}
		.load{
			display: inline-block;
			vertical-align: middle;
			font-size: 50px;
			color: #FFF;
		}
	</style>
</head>
<body onload="get_data()">
<!-- loading -->
<div id="load">
	<span ></span>
	<div class="load">加载中...</div>
</div>
<div id="body">
<h1>网站仅学习交流!!网站中的商品信息均来自于互联网。</h1>
<!-- 		<div id="search1">
		<input type="text" class="input1">
		<button class="button1">搜索</button><b> 网站仅学习交流!!网站仅学习交流!!网站仅学习交流!!网站仅学习交流!!网站仅学习交流!!</b>
	</div> -->
	<div id="nav"></div>
	<div id="content"></div>
</div>
</body>
</html>

完成上传json文件和html页面到GitHub

者 | 单雨

责编 | 胡巍巍

出品 | CSDN(ID:CSDNnews)

前言

为了实现模板封装和复用,提高HTML界面调试便捷性以及前后端解耦等目标,Django定义了自己的网络模板语言。

当前介绍模板语言的官方文档已经非常完备,几乎涵盖了开发中需要用到的知识点和需要注意的问题,但同时官方文档也存在一些问题:

  • 翻译不够完善,带来阅读的困难;

  • 一些知识点的介绍过于简短,存在大量的页内链接,阅读时需要跳转到不同的页面,阅读不连贯。

本文基于官方文档系统介绍了Django模板语言的基础知识点,方便快速了解Django模板语言。

模板系统设计哲学

Django的模板系统不是简单的把Python嵌入到HTML中。

它的设计宗旨是:模板系统旨在展示内容, 而不是程序逻辑,因此不在HTML页面中嵌入Python。

简单的说,模板只负责渲染数据,大多数逻辑应该交给视图(view)进行处理。

模板简介

模板是一个简单的文本文件。它可以生成任何基于文本的格式(如 HTML,XML,CSV等)。除了基本的HTML标签外,模板还包含两种额外的元素——变量和标签。

模板中包含的变量可以被替换为变量的值,标签则被替换为相应的模板控制逻辑。示例:

django

{% extends "base_generic.html" %}

{% block title %}{{ section.title }}{% endblock %}

{% block content %}

<h1>{{ section.title }}</h1>

{% for story in story_list %}

<h2>

<a href="{{ story.get_absolute_url }}">

{{ story.headline|upper }}

</a>

</h2>

<p>{{ story.tease|truncatewords:"100" }}</p>

{% endfor %}

{% endblock %}

`{{ section.title }}`在模板渲染时将会被变量的值替换,for标签可以实现模板的循环渲染。

基础语法

变量

变量实现从模板上下文字典(返回HTTP响应时传递过来的字典)中输出一个值,这是一个类似于dict的对象,包含键值对。当模板引擎遇到一个变量时,它会计算该变量,并用结果替换它。

变量名由字母、数字字符和下划线("_")组成,但不能以下划线开头。点(".")也出现在变量中,代表属性调用,变量名中不能有空格或标点符号。

示例:

django

My first name is {{ first_name }}. My last name is {{ last_name }}.

当传入一个上下文字典`{'first_name': 'John', 'last_name': 'Doe'}`时,将会渲染得到:

django

My first name is John. My last name is Doe.

模板中的变量被字典中的值替换了。

变量还可以使用点表示法实现字典查找、属性查找和列表索引查找等操作:

django

{{ my_dict.key }}

{{ my_object.attribute }}

{{ my_list.0 }}

点表示法底层原理

当模板系统遇到一个点,它会按顺序尝试下面的动作:

1. 字典查询

2. 属性或方法查找

3. 数字索引查询

如果结果值是可调用的,则调用该值时将不带参数,调用的结果成为新的模板值。

当进行能覆盖字典查找的操作时,这种查找顺序可能会造成一些意想不到的行为。例如:如果试图循环一个collection .defaultdict字典对象:

django

{% for k, v in defaultdict.items %}

{其他操作}

{% endfor %}

因为字典查找是首先发生的,所以这个行为会先提供一个默认值,而不是使用预期的.items方法。在这种情况下,应该首先考虑使用字典查找,而不是使用字典的属性调用。

注意

属性通常被解释为一个文本字符串,防止和同名的变量冲突。例如{{foo.bar}}中的属性“bar”将被解释为一个文本字符串,如果模板上下文中存在变量“bar”,则不会使用该变量的值。

以下划线开头的变量属性可能不能访问,因为它们通常被认为是私有的。

如果引用不存在的变量,模板系统将插入string_if_invalid选项的值,该选项默认设置为“”(空字符串)。

标签

标签在模板渲染过程中提供任意逻辑。标签可以输出内容,作为控制结构,例如“if”语句或“for”循环,从数据库获取内容,甚至允许访问其他模板标签。

(1)标签声明

标签的一般形式为:

django

{% tag %}

示例:

django

{% csrf_token %}

(2)传入参数

django

{% cycle 'odd' 'even' %}

(3)成对使用的标签

有些标签需要开始和结束标签:

django

{% if user.is_authenticated %}Hello, {{ user.username }}.{% endif %}

(4)常用标签

for:循环数组中的每个元素. 比如, 显示列表 `athlete_list` 中每个元素的 `name` 属性。

django

<ul>

{% for athlete in athlete_list %}

<li>{{ athlete.name }}</li>

{% endfor %}

</ul>

if 、elif和else:在上面,如果athlete_list不为空,则{{athlete_list|length}}变量将显示运动员的数量。

否则,如果athlete_in_locker_room_list不为空,则会显示“Athletes should be out…”消息。如果两个列表都为空,则显示“No athletes”。

也可以在if标签里使用过滤器和各种操作符:

django

{% if athlete_list|length > 1 %}

Team: {% for athlete in athlete_list %} ... {% endfor %}

{% else %}

Athlete: {{ athlete_list.0.name }}

{% endif %}

注意

虽然上面的示例可以工作,但是要注意,大多数模板过滤器都返回字符串,因此使用过滤器进行数学比较通常不会正常工作,而长度是个例外。

(5)更多

Django有很多内置标签,更多关于内置标签的信息请参考官方文档:

https://docs.djangoproject.com/zh-hans/2.2/ref/templates/builtins/ref-templates-builtins-tags

如果需要编写自定义标签,请参考官方文档

https://docs.djangoproject.com/zh-hans/2.2/howto/custom-template-tags/howto-writing-custom-template-tags

如果需要对使用的标签和自定义的标签做一份说明文档,可以使用Django提供的文档工具,详情请参考:

https://docs.djangoproject.com/zh-hans/2.2/ref/contrib/admin/admindocs/

过滤器

简介

过滤器可以对变量做一些操作,例如给变量赋值,改变变量的值等。

修改变量显示

过滤器可以修改变量的显示。例如:

django

{{ name|lower }}

通过过滤器lower变量{{ name }}变为了小写字符,通过管道符(|)间隔变量和过滤器来使用过滤器。

链式调用过滤器

一个过滤器的输出可以作为下一个过滤器的输入。

{{ text|escape|linebreaks }}是一种常用的转换方式, 在这之后换行符被替换为了 <p> 标签。

转换变量和标签参数

过滤器转换变量和标签参数的值。示例:

django

{{ django|title }}

传入`{'django': 'the web framework for perfecalist With deadline '}`上下文字典时,该模板呈现为:

django

The Web Framework For Perfectionists With Deadlines

传入参数给过滤器

示例1:

django

{{ my_date|date:"Y-m-d" }}

my_date将会被替换为当前日期。

示例2:

django

{{ bio|truncatewords:30 }}

将会会显示 `bio` 变量的前30个字符

注意

过滤器参数中如果包含空格和标点符号,必须使用引号“”括起来,例如,要用逗号和空格连接列表,可以使用{{list|join:", "}}。

Django提供了大约60个内置模板过滤器,请参考官方文档:

https://docs.djangoproject.com/zh-hans/2.2/ref/templates/builtins/ref-templates-builtins-filters

下面列举一些常用的过滤器:

default

如果变量为false或空,则使用给定的默认值。否则,使用变量的值。例如:

django

{{ value|default:"nothing" }}

如果 `value` 没有提供或者为空,那么将它显示为 "`nothing`" 。

length

返回值的长度。这对字符串和列表都适用。例如:

django

{{ value|length }}

如果 `value` 为 `['a', 'b', 'c', 'd']`, 那么他将被显示为 `4`。

filesizeformat

将值格式化为“人类可读的”文件大小(即“13kb”、“4.1 MB”、“102字节”等)。例如:

django

{{ value|filesizeformat }}

如果值为123456789,则输出为117.7 MB。

如果需要自定义过滤器,请参考请官方文档:

https://docs.djangoproject.com/zh-hans/2.2/howto/custom-template-tags/

注释

示例:

单行注释

django

{ this won't be rendered }

多行注释:{% comment %} 和{% endcomment %}

django

<p>Rendered text with {{ pub_date|date:"c" }}</p>

{% comment "Optional note" %}

<p>Commented out text with {{ create_date|date:"c" }}</p>

{% endcomment %}

注意:Comment标签不能嵌套使用。

作者简介:单雨,90后工科男,伪文艺青年。目前就读于北京理工大学宇航系,喜欢研究AI,网络爬虫,微信小程序以及机器人,痴迷于Coding,睡前必撸码。

【END】