专栏首页开发架构二三事golang小工具download公众号文章或其他网页图片

golang小工具download公众号文章或其他网页图片

写在前面的话:有时候网上看到一篇写的比较好的公众号文章或者是其他博客文章,想要转载或者添加到个人笔记中来时,如果文中插入的图片较多,就需要一张张地拷贝文章。为了更方便地处理这种情况,抽了点时间写了个小工具,希望能带来帮助。

代码结构

代码

package main

import (
    "context"
    "flag"
    "fmt"
    "github.com/PuerkitoBio/goquery"
    "github.com/axgle/mahonia"
    "github.com/chromedp/chromedp"
    "github.com/gofrs/uuid"
    "github.com/siddontang/go-log/log"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
    "time"
)

var configFile string
var pathMap map[string]string
var sourceContent string
var projectPath string

func main()  {
    //initCmd()
    //var err0 error = nil
    //if err0 = conf.LoadConf(configFile); err0 != nil {
    //  return
    //}
    dir, _ := os.Getwd()
    println(dir)
    projectPath = dir
    configFile = "./conf/download.conf"
    content,err0 := ioutil.ReadFile(configFile)
    byteContent,_ := ioutil.ReadFile("./conf/source.conf")
    sourceContent = string(byteContent)
    //fmt.Println(sourceContent)
    if err0 != nil {
        return
    }
    path := string(content)
    //创建map
    pathMap = make(map[string]string)
    ctxt,cancel := chromedp.NewContext(context.Background())
    defer cancel()

    var res string

    context.WithTimeout(ctxt,15 * time.Second)
    //https://ethfans.org/posts/wtf-is-the-blockchain
    site:= "https://ethfans.org/posts/wtf-is-the-blockchain"
    //site := "https://mp.weixin.qq.com/s/qnceG5MVwlFcm1FaHSKgWA"
    //downloadWebChat(ctxt, site, res, path)
    downloadSimpleHtml(ctxt,site,res,path)

}

func downloadSimpleHtml(ctxt context.Context, site string, res string, path string)  {
    err := chromedp.Run(ctxt,visibleSimpleHtml(site,&res))
    if err != nil {
        return
    }
    fmt.Println("===============" + res)
    reader := strings.NewReader(res)
    //解析utf-8格式的串
    dec := mahonia.NewDecoder("utf-8")
    rd := dec.NewReader(reader)
    doc,err := goquery.NewDocumentFromReader(rd)
    if err != nil {
        log.Fatal(err)
    }
    html, err := doc.Html()
    fmt.Println("=========" + html)
    channels := make(chan string)
    doc.Find("img").Each(func(i int, selection *goquery.Selection) {
        img_url,_ := selection.Attr("src")
        fmt.Println("imgUrl:" + img_url)
        if strings.Trim(img_url, " ") == "" {
            return
        }
        go download(img_url,channels,path)
        fmt.Println("src = " + <-channels + "图片爬取完毕")
    })
    //全部结束后,替换文件
    for originalPath,newPath := range pathMap {
        sourceContent = strings.Replace(sourceContent,originalPath,newPath,-1)
    }
    dir, _ := os.Getwd()
    //这里路径会变化,需要注意
    println(dir)
    //将新的sourceContent输出
    fmt.Println(sourceContent)
    er := ioutil.WriteFile(projectPath + "/conf/outputs.conf",[]byte(sourceContent),0644)
    if er != nil {
        //file, _ := exec.LookPath(os.Args[0])
        //path, _ := filepath.Abs(file)
        //println(path)
        //dir2, _ := os.Executable()
        //exPath := filepath.Dir(dir2)
        //println(exPath)
        log.Error(er)
    }
}

func downloadWebChat(ctxt context.Context, site string, res string, path string) {
    err := chromedp.Run(ctxt, visible(site, &res))
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("===========" + res)
    reader := strings.NewReader(res)
    dec := mahonia.NewDecoder("utf-8")
    rd := dec.NewReader(reader)
    doc, err := goquery.NewDocumentFromReader(rd)
    //获取将要爬取的html文档信息
    if err != nil {
        log.Fatal(err)
    }
    html, err := doc.Html()
    fmt.Println("=====" + html)
    //创建管道
    channels := make(chan string)
    doc.Find("img").Each(func(i int, selection *goquery.Selection) {
        img_url, _ := selection.Attr("data-src")
        fmt.Println("imgUrl:" + img_url)
        if strings.Trim(img_url, " ") == "" {
            return
        }
        if strings.Index(img_url, "https") == -1 {
            return
        }
        index := strings.Index(img_url, "?")
        if index != -1 {
            rs := []rune(img_url)
            newUrl := string(rs[0:index])
            go download(newUrl, channels, path)
        } else {
            go download(img_url, channels, path)
        }
        //从管道消费
        fmt.Println("src = " + <-channels + "图片爬取完毕")
    })
}

func visibleSimpleHtml(host string,res *string) chromedp.Tasks {
    //sel := "body > div.site-content > article > main"
    return chromedp.Tasks{
        chromedp.Navigate(host),
        chromedp.Sleep(3 * time.Second),
        chromedp.InnerHTML("body",res,chromedp.ByQuery),
    }
}



//func LoadConf(filename string) error {
//    content, err := ioutil.ReadFile(filename)
//    if err != nil {
//        return err
//    }
//
//    conf := Conf{}
//    err = json.Unmarshal(content, &conf)
//    if err != nil {
//        return err
//    }
//    GConf = &conf
//    return nil
//}

func initCmd() {
    flag.StringVar(&configFile, "config", "./config/download.conf", "where download.conf is.")
    flag.Parse()
}

func download(img_url string,channels chan string,path string) {
    fmt.Println("准备抓取:" + img_url)
    uid,_ := uuid.NewV4()
    file_name := uid.String() + ".png"
    base_dir := path
    file_dir := base_dir + "\\"
    exists,err := PathExists(file_dir)
    if err != nil {
        fmt.Printf("get dir error![%v]\n",err)
        return
    }
    if !exists {
        os.MkdirAll(file_dir,os.ModePerm)
    }
    os.Chdir(file_dir)
    f, err := os.Create(file_name)
    if err != nil {
        log.Panic("文件创建失败")
    }
    defer f.Close()

    resp, err := http.Get(img_url)
    if err != nil {
        fmt.Println("http.get err",err)
    }
    body,err1 := ioutil.ReadAll(resp.Body)
    if err1 != nil {
        fmt.Println("读取数据失败")
    }
    defer resp.Body.Close()
    f.Write(body)
    pathMap[img_url] = "img/" + file_name
    //成功后将文件名传入管道内
    channels <- "![](img/" + file_name + ")                 " + "sourceUrl is:" + img_url
}

func PathExists(path string) (bool,error) {
    _,err := os.Stat(path)
    if err == nil {
        return true , nil
    }
    if os.IsNotExist(err) {
        return false , nil
    }
    return false , err
}


/**
  设置获取的html区域
 */
func visible(host string,res *string) chromedp.Tasks {
    sel := "#page-content"
    return chromedp.Tasks{
        chromedp.Navigate(host),
        chromedp.Sleep(3 * time.Second),
        chromedp.InnerHTML(sel,res,chromedp.ByID),
    }
}

代码构建

直接运行main.go文件或者通过go build ./打成windows下的exe包或者在linux下打成downloadPic包直接运行

代码分析

代码逻辑主要分为以下几步:

  1. 解析配置,主要是配置图片下载的目录,如果需要对文章的路径进行替换,也可在conf/source.conf中配置需要替换路径的文章;
  2. 解析网址并对响应网页流进行解析,主要是对chromedp包的使用;
  3. 对网页中的img src处获取的路径进行解析,每个路径交给一个协程去处理,因为一篇文章不会太长,所以也可以直接当前线程来处理所有图片的下载;
  4. 在每个协程中负责图片的下载,并将需要替换的图片路径和要替换成的图片路径放入map中。
  5. 所有协程处理结束后,替换文章中的图片路径。

chromedp包的使用示例:

// 任务 主要用来设置cookie ,获取登录账号后的页面
func visitWeb(url string) chromedp.Tasks {

    return chromedp.Tasks{
        chromedp.ActionFunc(func(ctxt context.Context, h cdp.Executor) error {
            expr := cdp.TimeSinceEpoch(time.Now().Add(180 * 24 * time.Hour))
            success, err := network.SetCookie("ASP.NET_SessionId", "这里是值"). //设置cookie
                WithExpires(&expr).
                WithDomain("dl.gaggjz.pw:8086"). //访问网站主体
                WithHTTPOnly(true).
                Do(ctxt, h)
            if err != nil {
                return err
            }
            if !success {
                return errors.New("could not set cookie")
            }

            return nil
        }),
        chromedp.Navigate(url), //页面跳转
    }
}
// 任务 主要执行翻页功能和或者html
func DoCrawler() chromedp.Tasks {

    //sel =fmt.Sprintf(`javascript:__doPostBack('anpDataPager','%s')`,"2")

    return chromedp.Tasks{
        chromedp.Sleep(1*time.Second), // 等待
        chromedp.WaitVisible(`#form1`, chromedp.ByQuery),//等待id=from1页面可见  ByQuery是使用DOM选择器查找
        chromedp.Sleep(1*time.Second),
        chromedp.Click(`.pagination li:nth-last-child(4) a`, chromedp.ByQuery),//点击翻页
        chromedp.OuterHTML(`tbody`, &res, chromedp.ByQuery), //获取改 tbody标签的html
    }
}

本文分享自微信公众号 - 开发架构二三事(gh_d6f166e26398),作者:两个小灰象

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 实战之防止表单重复提交

    对于防止重复提交,最简单也最不安全的做法相信大家也都经历过,前端在一个请求发送后立即禁用掉按钮,这里咱们来讨论一下后端对防止重复提交的处理方式。 主要针对非分布...

    开发架构二三事
  • Elasticsearch源码分析八之锁管理工具KeyLock

    KeyedLock的map属性是存放资源标识和KeyLock的容器,也就是一个大的锁容器。KeyLock为每一个资源标识对应的锁对象,它继承自Reentrant...

    开发架构二三事
  • dubbo源码分析之filter加载机制

    然后在doExportUrlsFor1Protocol方法中会进行url的拼接操作,将一些参数拼接到url的后面,形式为ip:port/com.rt.Servi...

    开发架构二三事
  • Java数据结构-------Map

        1)无序; 2)访问速度快; 3)key不允许重复(只允许存在一个null Key);

    在周末
  • 帮你面试——HashMap

    这几天学习了HashMap的底层实现,但是发现好几个版本的,代码不一,而且看了Android包的HashMap和JDK中的HashMap的也不是一样,原来他们没...

    小忽悠
  • npoi批量导入实现及相关技巧

      批量导入功能对于大部分后台系统来说都是不可或缺的一部分,常见的场景-基础数据的录入(部门,用户),用批量导入方便快捷。最近项目需要用到批量导入,决定花点时间...

    用户1168362
  • 图片延迟加载

    官网:http://www.appelsiini.net/projects/lazyload 使用例子:http://demo.phpfs.com/lazylo...

    苦咖啡
  • Freemarker常用方法

    剑行者
  • 每天 3 分钟,小闫带你学 Python(七)

    今天暂时不分享英文名言,因为昨天晚上看到4句话,特别想分享给大家。第一句『物来顺应』告诫我们事情已经发生了,就要顺应去面对;第二句『未来不迎』劝告我们不要为没有...

    小闫同学啊
  • 牛客寒假算法基础集训营5 I. 炫酷镜子(模拟)

    题目链接:https://ac.nowcoder.com/acm/contest/331/I

    Ch_Zaqdt

扫码关注云+社区

领取腾讯云代金券