Golang爬虫:使用正则表达式解析HTML
之前所写的爬虫都是基于Python,而用Go语言实现的爬虫具有更高的性能。
第一个爬虫
使用http库,发起http请求
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func fetch(url string) string {
client := &http.Client{} // 创建http客户端对象
// 创建一个请求,使用get方法
req, _ := http.NewRequest("GET", url, nil)
// 设置请求头内容
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64)")
resp, err := client.Do(req) // 发起请求
if err != nil {
fmt.Println(err)
return ""
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return ""
}
return string(body)
}
func main() {
url := "https://www.cnblogs.com/N3ptune"
res := fetch(url)
fmt.Println(res)
}
运行这段代码,可以看到终端打印除了网页代码,这是服务端回应的内容
对上述几个函数作一个特别说明
http.NewRequest
$ go doc http.NewRequest
package http // import "net/http"
func NewRequest(method, url string, body io.Reader) (*Request, error)
NewRequest wraps NewRequestWithContext using context.Background.
该函数是构建请求的第一步
req.Header即Request Header,所谓请求头。req.Header.Set是设置请求头,可以用来应对一些反爬虫机制,如上是设置了User-Agent。
User-Agent 即用户代理,简称“UA”,它是一个特殊字符串头。网站服务器通过识别 “UA”来确定用户所使用的操作系统版本、CPU 类型、浏览器版本等信息。
http.Do
用于发送HTTP请求,同时会返回响应内容
$ go doc http.Do
package http // import "net/http"
func (c *Client) Do(req *Request) (*Response, error)
Do sends an HTTP request and returns an HTTP response, following policy
(such as redirects, cookies, auth) as configured on the client.
Body是响应的正文,不用时要关闭
ioutil.ReadAll
上述代码中用于读取响应中的正文内容,返回一个字节切片
$ go doc io.ReadAll
package io // import "io"
func ReadAll(r Reader) ([]byte, error)
ReadAll reads from r until an error or EOF and returns the data it read. A
successful call returns err == nil, not err == EOF. Because ReadAll is
defined to read from src until EOF, it does not treat an EOF from Read as an
error to be reported.
因为是字节类型的切片,所以要转成string
如下是main函数
func main() {
url := "https://www.cnblogs.com/N3ptune"
res := fetch(url)
fmt.Println(res)
}
解析页面
这里先使用正则表达式 目标URL:GORM 指南

目标是爬取到左侧每个条目的链接

查看网页源码就可以找到链接,显然这些链接都是用固定格式的,可以轻易使用正则表达式匹配。
func parse(html string) {
// 替换换行
html = strings.Replace(html, "\n", "", -1)
// 边栏内容块 正则
reSidebar := regexp.MustCompile(`<aside id="sidebar" role="navigation">(.*?)</aside>`)
sidebar := reSidebar.FindString(html)
// 链接 正则
reLink := regexp.MustCompile(`href="(.*?)"`)
// 找到所有链接
links := reLink.FindAllString(sidebar, -1)
baseURL := "https://gorm.io/zh_CN/docs/"
// 遍历链接
for _, v := range links {
s := v[6 : len(v)-1]
url := baseURL + s
fmt.Printf("URL: %v\n", url)
}
}
上面这个函数用于解析HTML文档,构造正则表达式进行匹配。
应用正则表达式要使用regexp模块
MustCompile
$ go doc regexp.MustCompile
package regexp // import "regexp"
func MustCompile(str string) *Regexp
MustCompile is like Compile but panics if the expression cannot be parsed.
It simplifies safe initialization of global variables holding compiled
regular expressions.
该函数的作用是解析正则表达式,因此它的参数是一个正则表达式字符串
FindString
$ go doc regexp.FindString
package regexp // import "regexp"
func (re *Regexp) FindString(s string) string
FindString returns a string holding the text of the leftmost match in s of
the regular expression. If there is no match, the return value is an empty
string, but it will also be empty if the regular expression successfully
matches an empty string. Use FindStringIndex or FindStringSubmatch if it is
necessary to distinguish these cases.
使用FindString函数可以按照正则规则匹配字符串,只返回一个字符串,该字符串包含最左边的匹配文本。
FindAllString 则返回多个字符串
$ go doc regexp.FindAllString
package regexp // import "regexp"
func (re *Regexp) FindAllString(s string, n int) []string
FindAllString is the 'All' version of FindString; it returns a slice of all
successive matches of the expression, as defined by the 'All' description in
the package comment. A return value of nil indicates no match.
首先使用正则表达式匹配边栏的内容块,找到内容块所在的标签,然后将其作为匹配规则,传入FindString函数。然后再构造另一个正则表达式,传入FindAllString函数来匹配链接,返回字符串切片。
对其进行遍历,即可得到链接
main函数
func main() {
url := "https://gorm.io/zh_CN/docs/index.html"
res := fetch(url)
parse(res)
}
运行结果
URL: https://gorm.io/zh_CN/docs/index.html
URL: https://gorm.io/zh_CN/docs/models.html
URL: https://gorm.io/zh_CN/docs/connecting_to_the_database.html
URL: https://gorm.io/zh_CN/docs/create.html
URL: https://gorm.io/zh_CN/docs/query.html
URL: https://gorm.io/zh_CN/docs/advanced_query.html
URL: https://gorm.io/zh_CN/docs/update.html
URL: https://gorm.io/zh_CN/docs/delete.html
URL: https://gorm.io/zh_CN/docs/sql_builder.html
URL: https://gorm.io/zh_CN/docs/belongs_to.html
URL: https://gorm.io/zh_CN/docs/has_one.html
URL: https://gorm.io/zh_CN/docs/has_many.html
URL: https://gorm.io/zh_CN/docs/many_to_many.html
URL: https://gorm.io/zh_CN/docs/associations.html
URL: https://gorm.io/zh_CN/docs/preload.html
URL: https://gorm.io/zh_CN/docs/context.html
URL: https://gorm.io/zh_CN/docs/error_handling.html
URL: https://gorm.io/zh_CN/docs/method_chaining.html
URL: https://gorm.io/zh_CN/docs/session.html
URL: https://gorm.io/zh_CN/docs/hooks.html
URL: https://gorm.io/zh_CN/docs/transactions.html
URL: https://gorm.io/zh_CN/docs/migration.html
URL: https://gorm.io/zh_CN/docs/logger.html
URL: https://gorm.io/zh_CN/docs/generic_interface.html
URL: https://gorm.io/zh_CN/docs/performance.html
URL: https://gorm.io/zh_CN/docs/data_types.html
URL: https://gorm.io/zh_CN/docs/scopes.html
URL: https://gorm.io/zh_CN/docs/conventions.html
URL: https://gorm.io/zh_CN/docs/settings.html
URL: https://gorm.io/zh_CN/docs/dbresolver.html
URL: https://gorm.io/zh_CN/docs/sharding.html
URL: https://gorm.io/zh_CN/docs/serializer.html
URL: https://gorm.io/zh_CN/docs/prometheus.html
URL: https://gorm.io/zh_CN/docs/hints.html
URL: https://gorm.io/zh_CN/docs/indexes.html
URL: https://gorm.io/zh_CN/docs/constraints.html
URL: https://gorm.io/zh_CN/docs/composite_primary_key.html
URL: https://gorm.io/zh_CN/docs/security.html
URL: https://gorm.io/zh_CN/docs/gorm_config.html
URL: https://gorm.io/zh_CN/docs/write_plugins.html
URL: https://gorm.io/zh_CN/docs/write_driver.html
URL: https://gorm.io/zh_CN/docs/changelog.html
URL: https://gorm.io/zh_CN/docs//community.html
URL: https://gorm.io/zh_CN/docs//contribute.html
URL: https://gorm.io/zh_CN/docs//contribute.html#Translate-this-site
进一步解析
下面对每个URL对应的页面再进行解析,获取到每个页面的标题
func getTitle(body string) {
// 替换换行
body = strings.Replace(body, "\n", "", -1)
// 页面内容
reContent := regexp.MustCompile(`<div class="article">(.*?)</div>`)
// 找到页面内容
content := reContent.FindString(body)
// 标题
reTitle := regexp.MustCompile(`<h1 class="article-title" itemprop="name">(.*?)</h1>`)
// 找到页面内容
title := reTitle.FindString(content)
if len(title) < 47 {
return
}
title = title[42 : len(title)-5]
fmt.Printf("title: %v\n", title)
}
访问目标URL,得到HTML文本,使用正则表达式,只要构造出标签的匹配规则即可。
用一个专门的goroutine去做这件事
运行结果
title: GORM 指南
title: 模型定义
title: 连接到数据库
title: 创建
title: 查询
title: 高级查询
title: 更新
title: 删除
title: SQL 构建器
title: Belongs To
title: Has One
title: Has Many
title: Many To Many
title: 实体关联
title: 预加载
title: Context
title: 处理错误
title: 链式方法
title: 会话
title: Hook
title: 事务
title: 迁移
title: Logger
title: 常规数据库接口 sql.DB
title: 性能
title: 自定义数据类型
title: Scopes
title: 约定
title: 设置
title: DBResolver
title: Sharding
title: Serializer
title: Prometheus
title: Hints
title: 数据库索引
title: 约束
目前的完整代码
package main
import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
)
func fetch(url string) string {
client := &http.Client{} // 创建http客户端
// 创建一个请求
req, _ := http.NewRequest("GET", url, nil)
// 设置请求头内容
req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64)")
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return ""
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return ""
}
return string(body)
}
func parse(html string) {
// 替换空格
html = strings.Replace(html, "\n", "", -1)
// 边栏内容块正侧
reSidebar := regexp.MustCompile(`<aside id="sidebar" role="navigation">(.*?)</aside>`)
// 找到所有链接
sidebar := reSidebar.FindString(html)
// 链接正则
reLink := regexp.MustCompile(`href="(.*?)"`)
// 找到所有链接
links := reLink.FindAllString(sidebar, -1)
baseURL := "https://gorm.io/zh_CN/docs/"
// 遍历链接
for _, v := range links {
s := v[6 : len(v)-1]
url := baseURL + s
body := fetch(url)
go getTitle(body)
}
}
func getTitle(body string) {
// 替换掉空格
body = strings.Replace(body, "\n", "", -1)
// 页面内容
reContent := regexp.MustCompile(`<div class="article">(.*?)</div>`)
// 找到页面内容
content := reContent.FindString(body)
// 标题
reTitle := regexp.MustCompile(`<h1 class="article-title" itemprop="name">(.*?)</h1>`)
// 找到页面内容
title := reTitle.FindString(content)
if len(title) < 47 {
return
}
title = title[42 : len(title)-5]
fmt.Printf("title: %v\n", title)
}
func main() {
url := "https://gorm.io/zh_CN/docs/index.html"
res := fetch(url)
parse(res)
}
后续还会考虑将数据保存到本地
Golang爬虫:使用正则表达式解析HTML的更多相关文章
- Python爬虫 | re正则表达式解析html页面
正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符"). 正则表达式通常被用来匹配.检索.替换和 ...
- 基础知识 - Golang 中的正则表达式
------------------------------------------------------------ Golang中的正则表达式 ------------------------- ...
- python 3.x 爬虫基础---正则表达式
python 3.x 爬虫基础 python 3.x 爬虫基础---http headers详解 python 3.x 爬虫基础---Urllib详解 python 3.x 爬虫基础---Requer ...
- Golang - 爬虫案例实践
目录 Golang - 爬虫案例实践 1. 爬虫步骤 2. 正则表达式 3. 并发爬取美图 Golang - 爬虫案例实践 1. 爬虫步骤 明确目标(确定在哪个网址搜索) 爬(爬下数据) 取(去掉没用 ...
- python爬虫---爬虫的数据解析的流程和解析数据的几种方式
python爬虫---爬虫的数据解析的流程和解析数据的几种方式 一丶爬虫数据解析 概念:将一整张页面中的局部数据进行提取/解析 作用:用来实现聚焦爬虫的吧 实现方式: 正则 (针对字符串) bs4 x ...
- java对身份证验证及正则表达式解析
原文地址:http://www.cnblogs.com/zhongshengzhen/ java对身份证验证及正则表达式解析 package service; import java.text.Par ...
- 用正则表达式解析XML
import java.util.regex.*; import java.util.*; /** * * <p>Title: Document</p> * * <p&g ...
- Golang爬虫示例包系列教程(一):pedaily.com投资界爬虫
Golang爬虫示例包 文件结构 自己用Golang原生包封装了一个爬虫库,源码见go get -u -v github.com/hunterhug/go_tool/spider ---- data ...
- golang reflect包使用解析
golang reflect包使用解析 参考 Go反射编码 2个重要的类型 Type Value 其中Type是interface类型,Value是struct类型,意识到这一点很重要 Type和Va ...
- 笔记-爬虫-js代码解析
笔记-爬虫-js代码解析 1. js代码解析 1.1. 前言 在爬取网站时经常会有js生成关键信息,而且js代码是混淆过的. 以瓜子二手车为例,直接请求https://www.guaz ...
随机推荐
- Linux 配置nginx 代理tomcat,配置ssl
我就直接干活不废话: 配置文件nginx, nginx.conf #user nobody;worker_processes 1; #error_log logs/error.log;#error_l ...
- pg_dump导出表时正则匹配多个表,pg_dump导出表
报错信息:pg_dump: 错误: 没有找到符合的表 报错语句:C:\Users\Admin>pg_dump -h172.16.3.159 -p5432 -dchisapp -nmchs -Um ...
- 【jupyter notebook】配置基于ecs云平台的jupyter notebook远程使用
学校提供了ecs云服务器进行高性能运算 分配到了云主机后想在上面搭好jupyter notebook服务器方便远程使用,在此记录整个流程和遇到的问题 本文档适用于常规的jupyter notebook ...
- (二).JavaScript的运算符和表达式,数据类型转化
4. 运算符和表达式 4.3 赋值运算符和表达式 1.赋值运算符 = 作用:赋值运算符就是将右边的内容赋值给左边的变量或属性. var result = 1 + 2; 2.复合赋值运算符 +=,-=, ...
- LeetCode 94. 二叉树的中序遍历()
原题解 题目 约束 题解 方法一 class Solution { public: void inorder(TreeNode* root, vector<int>& res) { ...
- 日志注解,基于ruoyi的后置切面改进而来
有次接口响应时间太长,想知道具体接口执行的时间是多少,于是决定通过注解来实现这个想法,刚好ruoyi本身就提供了完善的日志注解,虽然是采用后置通知,但是完全不影响我们改造它. 想要实现接口耗时的功能, ...
- ffmpeg+nginx+hls(低延迟)
先看一下我参考的原文实现: 实现方案 https://zhuanlan.zhihu.com/p/87225094 流媒体服务器环境搭建 https://blog.csdn.net/impingo/ar ...
- [jointjs] 端口(port)
关于端口,我也不知道怎么解释,就用joint官网的这句话先打个头. Many diagramming applications deal with the idea of elements with ...
- MySQL 索引的介绍
参考:https://www.cnblogs.com/aspirant/p/9214485.html 参考:https://blog.codinglabs.org/articles/theory-of ...
- ElementPlus 表单 resetFields 无效问题解决方法
最近在写一个项目,一个表单递交或者使用resetFields关闭后,再打开,原来的值还存在,后查了一下网上的方法,确定是el-form-item,必须要加prop,其值要与model相同,此问题得到完 ...