整合营销服务商

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

免费咨询热线:

Gin(三):与模板配合使用 tmpl,go web 开发最火框架之一

过Gin(二):路由Router ,go语言框架学习的学习,已经对 Gin 有了一个初步的认识和了解,对 router 的简单使用也有了一定的掌握,那么今天就来使用 Gin 来渲染出我们的 html 页面吧。

添加模板

我们仍旧是在上一章节的项目中进行修改。

首先新建一个 templates 文件夹,用于存放我们的模板文件,在文件夹中新建立 index.tmpl。并且编写我们的模板。

 <!doctype html>
 <html lang="en">
 <head>
 <meta charset="UTF-8">
 <meta name="viewport"
 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>Gin Hello</title>
 </head>
 <body>
 <main>{{ .title }}</main>
 </body>
 </html>

小提示 GoLand 现在还未对 tmpl 有高亮显示语法,我们可以将 .tmpl 后缀文件添加到 go Template 模板。方法如下:File-settings-Editor-File Types。(或者我们直接使用 *.gohtml 做为模板也是可以的)如图:

我们可以通过 LoadHTMLGlob 和 LoadHTMLFiles 两个方法来对我们的模板进行加载。其中 LoadHTMLGlob 方法可以将一个目录下所有的模板进行加载,而LoadHTMLFiles只会加载一个文件,他的参数为可变长参数,需要我们一个一个的手动将模板文件填写。这里我们使用 LoadHTMLGlob 方法。

 router.LoadHTMLGlob("templates/*")

此时我们需要修改我们的 / 路由了,不再是让它返回一个字符串,而是返回我们的页面模板。

在 handler 中新建立一个 indexHandler.go ,用来处理我们的 / 路由。

 func Index(context *gin.Context) {
 context.HTML(http.StatusOK, "index.tmpl", gin.H{
 "title": "hello gin " + strings.ToLower(context.Request.Method) + " method",
 })
}

此时在访问我们的页面,仍旧是 hello gin get method,但是这和我们之前返回的字符串是不同的。

打开 浏览器-Network 就可以看到这两个页面的不同,一个是 text 一个是 html

写完具体功能,我们需要改造一下我们的单元测试,针对新的接口逻辑原有的单元测试已经行不通了。

重新修改单元测试。

 func TestIndexHtml(t *testing.T) {
 router := initRouter.SetupRouter()
 w := httptest.NewRecorder()
 req, _ := http.NewRequest(http.MethodGet, "/", nil)
 router.ServeHTTP(w, req)
 assert.Equal(t, http.StatusOK, w.Code)
}

进行单元测试的时候,发现报错了,报错原因是竟然是找不到 html。

 --- FAIL: TestIndexGetRouter (0.00s)
 panic: html/template: pattern matches no files: `templates/*` [recovered]
 panic: html/template: pattern matches no files: `templates/*`

此时就感到很奇怪了,为什么网页上可以很好的访问到,在测试中却无法访问呢?

查了很多资料没有找到原因,很多解决方法是将 templates 写成全路径,这显然不是很好的解决方法。官方对于单元测试介绍少的可怜。

这里我介绍一种方法:

我们需要修改方法,通过判断不同的模式(debug,release,test)来加载不同路径下的 templates。

initRouter.go

 // 省略部分代码 
 if mode := gin.Mode(); mode == gin.TestMode {
 router.LoadHTMLGlob("./../templates/*")
 } else {
 router.LoadHTMLGlob("templates/*")
}

同时,也需要修改 test,我们要写个 init 方法,这里的 init 方法就好比 java 中 Junit 中的 @Before

 package test
 ​
 import (
 "GinHello/initRouter"
 "github.com/gin-gonic/gin"
 "github.com/stretchr/testify/assert"
 "net/http"
 "net/http/httptest"
 "testing"
 )
 ​
 var router *gin.Engine
 ​
 func init() {
 gin.SetMode(gin.TestMode)
 router = initRouter.SetupRouter()
 }
 ​
 func TestIndexHtml(t *testing.T) {
 w := httptest.NewRecorder()
 req, _ := http.NewRequest(http.MethodGet, "/", nil)
 router.ServeHTTP(w, req)
 assert.Equal(t, http.StatusOK, w.Code)
 assert.Contains(t,w.Body.String(),"hello gin get method","返回的HTML页面中应该包含 hello gin get method")
 }

这样完善了单元测试,方便日后的功能更改。

✨添加静态资源

当网页可以正常显示的时候,我们可以添加一些静态资源来使我们的页面更加美观。我在这里选择了 Bootstrap 4 做为一个 UI 界面框架。Bootstrap 4 下载

同时还要引入 Jquery 和 Popper。

我们新建一个 statics 文件夹,将我们 Bootstrap 解压,将 js 和 css 复制到 statics 目录下。

在我们的 initRouter 中,对静态资源进行添加。

 router.Static("/statics","./statics")

新建 header.tmpl

 {{ define "header" }}
 <nav class="navbar navbar-expand-lg navbar-light bg-light">
 <a class="navbar-brand" href="#">Gin Hello</a>
 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
 aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
 <span class="navbar-toggler-icon"></span>
 </button>
 ​
 <div class="collapse navbar-collapse" id="navbarSupportedContent">
 <ul class="navbar-nav mr-auto">
 <li class="nav-item active">
 <a class="nav-link" href="#">主页 <span class="sr-only">(current)</span></a>
 </li>
 <li class="nav-item">
 <a class="nav-link" href="#">文章列表</a>
 </li>
 <li class="nav-item">
 <a class="nav-link " href="#" tabindex="-1" aria-disabled="true">关于</a>
 </li>
 </ul>
 <form class="form-inline my-2 my-lg-0">
 <input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
 <button class="btn btn-outline-success my-2 my-sm-0" type="submit">搜索</button>
 </form>
 <ul class="navbar-nav ">
 <li class="nav-item">
 <a class="nav-link" href="#">登录</a>
 </li>
 <li class="nav-item">
 <a class="nav-link" href="#">注册</a>
 </li>
 </ul>
 </div>
 </nav>
 {{end}}

并在我们的 index.tmpl 上进行添加

 <body>
 <header>
 {{template "header"}}
 </header>
 <main>
 {{ .title }}
 </main>
 </body>

此时运行我们的网站,出现下图样式。

添加网站图标

万事俱备,只欠一个 icon。

网站此时还缺少网站的图标,网站图标也是通过 Gin 给定的函数设置。我们将 .ico 图片放到项目根目录下,然后在 initRouter 中设置。

 router.StaticFile("/favicon.ico","./favicon.ico")

再次运行我们的网站,此时就会发现网站的左上角标签页中出现了设置好的图标。

✍总结

通过本章节的介绍和学习,可以很快的使用 Gin 中的静态资源,定义网站图标等。

‍本章节代码

Github 点击了解更多查看全部代码

推荐阅读

Gin(一):Hello Gin ,学习 Gin 从这里开始

Gin(二):路由Router ,go语言框架学习

Gin(三):与模板配合使用 tmpl,go web 开发最火框架之一

Gin(四):接收表单数据和模型绑定,Go语言最火web框架之一

Gin(五):连接MySQL , Go 轻量级框架 Gin

Gin(六):文件的上传,go语言最火框架之一

Gin(七):中间件的介绍和使用,GO语言最火的框架之一

Gin(八):cookies使用「不做标题党,让你看后有所收获」

Gin(九):生成restful 接口,go语言最火web框架之一

Gin(十): 集成 Swagger,Gin配置这个神器,再也不用写接口文档了

程和编码概念

使用docker一键启动!

docker run -d -e REDIS_HOST=192.168.200.150 cnaafhvk/proxy-factory product start

如果你不喜欢用docker的话,那么请往下看。

关注后私信小编 PDF领取十套电子文档书籍

安装

# python3 以上版本
pip install proxy-factory
# 依赖 redis(必须), tesseract-ocr(可选)

运行

product start

1分钟后看看你本地redis中good_proxies字段,是不是已经有了不少代理?

超简单有木有

但是!

理想很丰满,现实很骨感。如果你运气好一个坑都没有踩到,那么请点右上角(或左上角)x,我们下次再见。但是我很清楚,99%的盆友都会遇到各种安装坑,所以如何躲坑也是我下面即将讲述的重点,支起耳朵来吧!

首先

程序使用到了redis做为存储,如果你电脑上正好安装有redis同时也启动着,那么恭喜你,这个坑不会撂倒你。如果没有,那么请安装

#ubutnu & pi
apt-get install redis-server

其它平台请自行google。

其次

程序使用到了 tesseract-ocr 这个google开源的验证码识别程序,代理网站mimvp很坑爹,端口使用图片,于是乎只能机器识别了。当然,不安装ocr也没有关系,无非放弃这个网站喽。

#ubutnu & pi
apt-get install tesseract-ocr

最后

还是上面那个垃圾网站的坑,因为要识别验证码,我对图片进行了处理,所以需要pillow,pillow的安装请自行查看pillow官网,如果想放弃这个网站,打开我的源码,把这个网站相关的代码注释掉即可。

如果你是windows平台,相信我,千万坑总有一个能把你撂倒,珍爱生命,远离windows。

好了,坑讲解完毕。

下面讲启动方式。

启动方式

程序虽小,功能挺全。

先贴下指令

usage: proxy factory [-h] [-s SETTINGS] [-cm CHECK_METHOD] [-sm SPIDER_MODULE]
 [-d]
 [{stop,start,restart,status}]
positional arguments:
 {stop,start,restart,status}
optional arguments:
 -h, --help show this help message and exit
 -s SETTINGS, --settings SETTINGS
 local settings.
 -cm CHECK_METHOD, --check-method CHECK_METHOD
 proivde a check method to check proxies.
 eg:module.func
 -sm SPIDER_MODULE, --spider-module SPIDER_MODULE
 proivde a module contains proxy site spider methods.
 eg:module
 -d, --daemon
####################################################################
- product start: 程序开始(阻塞式)
- product -d start: 程序开始(守护进程模式)
- product restart 程序重启(守护进程模式)
- product stop 程序关闭(守护进程模式)
- product status 程序状态(守护进程模式)
- product -s localsettings 指定一个自定义配置模块。(只要在sys.path中就可以找到)
- product -cm check-method 指定一个自定义检查方法。(只要在sys.path中就可以找到)
- product -sm spider-module 指定一个自定义的spider模块,存放自定义的spider方法。(只要在sys.path中就可以找到)

自定义检查方法

def check(self, proxy):
 """
 自义定检查方法
 :param self: ProxyFactory对象
 :param proxy: 代理
 :return: True则代理可用,否则False
 """
 import requests
 resp = requests.get("http://2017.ip138.com/ic.asp", proxies={"http": "http://%s"%proxy})
 self.logger.info(resp.text)
 ....
 return resp.status_code < 300

将检查方法保存成python文件,比如check,然后-cm check.check指向他即可。

自定义代理网站

def fetch_custom(self, page=5):
 """
 自定义代理网站抓取
 :param self:ProxyFactory对象
 :param page: 可以在里记录一些可选参数,但是方法只能接收一个必选参数
 :return: set类型的代理列表,ip:port
 """
 proxies = set()
 url_tmpl = "http://www.kxdaili.com/dailiip/1/%d.html"
 for page_num in range(page):
 url = url_tmpl % (page_num + 1)
 soup = BeautifulSoup(get_html(url, self.headers), "html")
 table_tag = soup.find("table", attrs={"class": "segment"})
 trs = table_tag.tbody.find_all("tr")
 for tr in trs:
 tds = tr.find_all("td")
 ip = tds[0].text
 port = tds[1].text
 latency = tds[4].text.split(" ")[0]
 if float(latency) < 0.5: # 输出延迟小于0.5秒的代理
 proxy = "%s:%s" % (ip, port)
 proxies.add(proxy)
 return proxies

将代理网站方法保存成python文件,比如proxy_sites.py 然后-sm proxy_site指向他即可。

配置模块

REDIS_HOST = "0.0.0.0"
REDIS_PORT = 6379
# 质量不好的代理检查的时间间隔
BAD_CHECK_INTERVAL = 60
# 质量不好的代理连续检查失败次数的最大值,超过则丢弃
FAILED_TIMES = 5
# 质量好的代理检查的时间间隔
GOOD_CHECK_INTERVAL = 60
# 抓取新代理的时间间隔
FETCH_INTERVAL = 60
LOG_LEVEL = 'DEBUG'
LOG_MAX_BYTES = 1024*1024*10
LOG_BACKUPS = 5
LOG_DIR = "/home/pi/logs"
LOG_STDOUT = False
LOG_JSON = False 

将配置模块信息保存成localsettings.py,然后-s 指向他就可以。同时,程序还支持环境变量配置,只要将字段保存为环境变量信息,如 export GOOD_CHECK_INTERVAL = 120,配置即可生效,优先级关系:环境变量>localsettings>defaultsettings。

redis中的状态

127.0.0.1:6379> keys *
1) "good_proxies"
2) "bad_proxies"
127.0.0.1:6379> type good_proxies
set
127.0.0.1:6379> type bad_proxies
hash
127.0.0.1:6379> smembers good_proxies
 1) "110.77.227.20:51552"
 2) "46.8.243.89:65205"
 3) "118.193.107.175:80"
 4) "223.99.214.21:53281"
 5) "119.41.200.20:53281"
 6) "120.198.224.7:8080"
 7) "110.77.177.116:51552"
 8) "36.37.219.198:53281"
 9) "138.197.230.116:55555"
10) "219.138.58.59:3128"
11) "192.158.236.58:3128"
12) "58.26.10.67:8080"
13) "60.208.44.228:80"
14) "120.198.224.5:8080"
15) "103.231.218.126:53281"
16) "186.215.148.228:80"
17) "210.26.125.142:8080"
18) "31.145.111.12:65103"
19) "218.201.98.196:3128"
20) "61.160.208.222:8080"
21) "101.200.45.131:3128"
22) "120.198.224.6:8080"
23) "52.66.155.0:80"
24) "195.234.87.211:53281"
25) "192.117.146.110:80"
26) "106.14.51.145:8118"
27) "106.75.25.3:80"
28) "177.24.31.129:8080"
29) "203.153.113.226:51552"
30) "118.193.107.222:80"
31) "112.13.93.43:8088"
32) "114.215.103.121:8081"
33) "118.193.107.240:80"
34) "111.56.5.41:80"
35) "118.193.107.138:80"
36) "118.193.107.182:80"
37) "183.215.140.117:8118"
38) "110.77.227.35:51552"
39) "137.74.254.242:3128"
40) "110.77.239.184:51552"
41) "94.177.201.18:80"
42) "110.77.210.182:51552"
43) "202.51.188.170:53281"
44) "124.133.230.254:80"
45) "120.198.224.5:8088"
46) "118.193.107.180:80"
47) "121.69.45.162:8118"
48) "118.193.107.100:80"
127.0.0.1:6379> 

9个代理网站,有反爬机制的只有2个,一个是mimvp,使用图片端口反爬,一个是goubanjia,使用js混淆反爬,有兴趣的可以研究一下。

我一直在使用一个小工具,并发现它在构建Javascript应用过程中非常实用。它是一个非常简单的模板函数,速度快,支持缓存,并容易使用。我想分享一下使用它的过程中的一些技巧。

  以下是模板函数的代码(你可以从正要出版的Secrets of the JavaScript Ninja一书中得到更精炼的版本):

// 简单JavaScript模板引擎
// John Resig – http://ejohn.org/ – MIT Licensed
(function(){
var cache = {};
this.tmpl = function tmpl(str, data){
// 判断我们是否已经有这么一个模板,或者我们需要载入模板
// 并保证把结果保存到缓存中。
var fn = !/W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :

// 生成一个可重用的函数,用于提供模板生成功能
// (它会被记录到缓存内).
new Function(“obj”,
“var p=[],print=function(){p.push.apply(p,arguments);};” +

// 通过with(){}把数据作为本地变量引入
“with(obj){p.push(‘” +

// 把模板转换未纯javascript代码
str
.replace(/[rtn]/g, ” “)
.split(“<%”).join(“t”)
.replace(/((^|%>)[^t]*)’/g, “r”)
.replace(/t=(.*?)%>/g, “‘,,'”)
.split(“t”).join(“‘);”)
.split(“%>”).join(“p.push(‘”)
.split(“r”).join(“\'”)
+ “‘);}return p.join(”);”);

// 给用户提供一些基本的柯里化功能
return data ? fn( data ) : fn;
};
})();

  你的模板代码看起来将是类似于(这并不是规定的格式,但是我比较喜欢这样):

<script type=”text/html” id=”item_tmpl”>
<div id=”<%=id%>” class=”<%=(i % 2 == 1 ? ” even” : “”)%>”>
<div class=”grid_1 alpha right”>
<img class=”righted” src=”<%=profile_image_url%>”/>
</div>
<div class=”grid_6 omega contents”>
<p><b><a href=”/<%=from_user%>”><%=from_user%></a>:</b> <%=text%></p>
</div>
</div>
</script>

  你也可以内嵌脚本:

<script type=”text/html” id=”user_tmpl”>
<% for ( var i = 0; i < users.length; i++ ) { %>
<li><a href=”<%=users[i].url%>”><%=users[i].name%></a></li>
<% } %>
</script>

  提示:把脚本内嵌到你的页面中,并且content-type是未知的(例如在这个例子中,浏览器不知道该如何执行 text/html 脚本),那么浏览器会把它忽略掉 – 同时搜索引擎和屏幕读取也会忽略掉它。这是一个非常好的伪装代码,可以把你的模板嵌入到你的页面中。我喜欢使用快速却又随性的技术,我只需一到两个小模板,就可以得到又轻型和快速应用。你可以在脚本中,像这样去使用:

var results = document.getElementById(“results”);
results.innerHTML = tmpl(“item_tmpl”, dataObject);

  你可以预编译结果,在稍后使用。如果你只使用一个ID作为参数(或者一个模板代码)来调用模板函数,那么它就会返回一个预编译的函数,你就可以在稍后调用:

var show_user = tmpl(“item_tmpl”), html = “”;
for ( var i = 0; i < users.length; i++ ) {
html += show_user( users[i] );
}

  这是目前最没办法的办法,解析和转换代码——对这你很可能无爱。不过他只有我中意的一项技术:在字符串中用字符静态搜索和静态替换时,比如split(“match”).join(“replace”),执行速度更好——看起来不直观但可以有效工作在现代浏览器里(下一版FireFox会大幅提高replace(/match/g,”replace”)的性能——所以现在这样的长表达式以后不需要的)。

  放轻松享受它——我很好奇代码中的突变。即使它很简单,仍然有很多事情可以用它做。