怀瑾以南辞

怀瑾以南辞 查看完整档案

厦门编辑武夷学院  |  物联网工程 编辑咖啡  |  前端开发工程师 编辑 github.com/LiucyLink?tab=repositories 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

怀瑾以南辞 赞了回答 · 2020-11-17

解决js中"!!"这个是什么意思呢?

同上都是套路。
下面我列举一下相关常用套路:

// 强制转换为Boolean 用 !!
var bool = !!"c";
console.log(typeof bool); // boolean

// 强制转换为Number 用 +
var num = +"1234";
console.log(typeof num); // number

// 强制转换为String 用 ""+
var str = ""+ 1234;
console.log(typeof str); // string


这只是部分也是比较取巧的方法,为了可读性建议还是按照官方语法写。我刚接触前端,回答得不对的请大家及时指正。刚到Segmentfault,还求个赞,谢谢

关注 17 回答 10

怀瑾以南辞 收藏了文章 · 2020-11-11

基于Node.js和node-xlsx开发一个Excel组件

简述需求

我们在用Excel表格的时候经常需要合并数据,比如统计整个年级的数据的时候,需要合并每个班成绩的时候,这就需要一个快速将很多张数据类型相同表合并的组件

需要的配置

  • 现在Node.js能够很好进行文件的读写操作,只要能读写就能合并
  • 社区已经有了封装好的一些插件比如node-xlsx,cnpm install就能用
  • 引入Node本来的文件读写工具
const xlsx = require('node-xlsx')
const fs = require('fs')

获取文件决定路径,定义合并数组

我们将文件放到excle里,而合并的结果放到result里

const _file = `${__dirname}/excel/`
const _output = `${__dirname}/result/`
let dataList = [
    {
        name: '提交成绩',//你sheet的名字
        data: []
    }]

读出数据

fs.readdir(_file, function(err, files) {}里可以将所有_file读出来放到files数组里


解析数据

let excelData = xlsx.parse(`${_file}${item}`)//拼接的绝对路径
console.log(excelData)
console.log(excelData[0].data)


提取数据进行拼接

files.forEach((item, index) => {
            try {
                console.log(`开始合并:${item}`)
                let excelData = xlsx.parse(`${_file}${item}`)
            if (excelData) {
                    if (dataList[0].data.length > 0) {
                        excelData[0].data.splice(0, 1)
   
                    }
                    dataList[0].data = dataList[0].data.concat(excelData[0].data)
                }
            } catch (e) {
                console.log('excel表格内部字段不一致,请检查后再合并。')
            }
        })

写入新的文件

        var buffer = xlsx.build(dataList)
        fs.writeFile(`${_output}resut.${new Date().getTime()}.xlsx`, buffer, function (err) {
            if (err) {
                throw err
            }
            console.log('\x1B[33m%s\x1b[0m', `完成合并:${_output}resut.${new Date().getTime()}.xlsx`)
        })

完整代码

const xlsx = require('node-xlsx')
const fs = require('fs')
// excel文件夹路径(把要合并的文件放在excel文件夹内)
const _file = `${__dirname}/excel/`
const _output = `${__dirname}/result/`
let dataList = [
    {
        name: '提交成绩',
        data: []
    }]

init()
function init () {
    fs.readdir(_file, function(err, files) {
        console.log(files)
        if (err) {
            throw err
        }
        files.forEach((item, index) => {
            try {
                console.log(`开始合并:${item}`)
                let excelData = xlsx.parse(`${_file}${item}`)
            if (excelData) {
                    if (dataList[0].data.length > 0) {
                        excelData[0].data.splice(0, 1)
                    }
             
                    dataList[0].data = dataList[0].data.concat(excelData[0].data)
                }
            } catch (e) {
                console.log('excel表格内部字段不一致,请检查后再合并。')
            }
        })
        var buffer = xlsx.build(dataList)
        fs.writeFile(`${_output}resut.${new Date().getTime()}.xlsx`, buffer, function (err) {
            if (err) {
                throw err
            }
            console.log('\x1B[33m%s\x1b[0m', `完成合并:${_output}resut.${new Date().getTime()}.xlsx`)
        })
    })
}
查看原文

怀瑾以南辞 收藏了文章 · 2020-11-05

分分钟教你用node.js写个爬虫

分分钟教你用node.js写个爬虫

写在前面

十分感谢大家的点赞和关注。其实,这是我第一次在segmentfault上写文章。因为我也是前段时间偶然之间才开始了解和学习爬虫,而且学习node的时间也不是很长。虽然用node做过一些后端的项目,但其实在node和爬虫方面我还是一个新人,这篇文章主要是想和大家分享一下node和爬虫方面的基本知识,希望对大家有帮助,也想和大家一起交流,一起学习,再次谢谢大家的支持!

对了,我开通了个人的 个人主页 ,里面有自己的技术文章,还会有个人的随想、思考和日志。以后所有的文章都会第一时间更新到这里,然后同步到其他平台。有喜欢的朋友可以没事去逛逛,再次感谢大家的支持!

一、什么是爬虫

网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。
WIKIPEDIA 爬虫介绍

二、爬虫的分类

  • 通用网络爬虫(全网爬虫)
爬行对象从一些 种子URL 扩充到整个 Web,主要为门户站点搜索引擎和大型 Web 服务提供商采集数据。

通用爬虫工作流程

  • 聚焦网络爬虫(主题网络爬虫)
指选择性 地爬行那些与预先定义好的主题相关页面的网络爬虫。
  • 增量式网络爬虫
指对已下载网页采取增量式更新和 只爬行新产生的或者已经发生变化网页 的爬虫,它能够在一定程度上保证所爬行的页面是尽可能新的页面。
  • Deep Web 爬虫
爬行对象是一些在用户填入关键字搜索或登录后才能访问到的深层网页信息的爬虫。

三、爬虫的爬行策略

  • 通用网络爬虫(全网爬虫)
深度优先策略、广度优先策略

页面关系模拟树状结构

  • 聚焦网络爬虫(主题网络爬虫)
基于内容评价的爬行策略(内容相关性),基于链接结构评价的爬行策略、基于增强学习的爬行策略(链接重要性),基于语境图的爬行策略(距离,图论中两节点间边的权重)
  • 增量式网络爬虫
统一更新法、个体更新法、基于分类的更新法、自适应调频更新法
  • Deep Web 爬虫
Deep Web 爬虫爬行过程中最重要部分就是表单填写,包含两种类型:基于领域知识的表单填写、基于网页结构分析的表单填写

现代的网页爬虫的行为通常是四种策略组合的结果:

选择策略:决定所要下载的页面;
重新访问策略:决定什么时候检查页面的更新变化;
平衡礼貌策略:指出怎样避免站点超载;
并行策略:指出怎么协同达到分布式抓取的效果;

现代分布式爬虫系统

四、写一个简单网页爬虫的流程

  1. 确定爬取对象(网站/页面)
  2. 分析页面内容(目标数据/DOM结构)
  3. 确定开发语言、框架、工具等
  4. 编码 测试,爬取数据
  5. 优化

一个简单的百度新闻爬虫

确定爬取对象(网站/页面)

百度新闻http://news.baidu.com/

分析页面内容(目标数据/DOM结构)

······

确定开发语言、框架、工具等

node.js (express) + SublimeText 3

编码,测试,爬取数据

coding ···

Let's start

新建项目目录

1.在合适的磁盘目录下创建项目目录baiduNews(我的项目目录是:F:\web\baiduNews

注:因为在写这篇文章的时候用的电脑真心比较渣。安装WebStorm或者VsCode跑项目有些吃力。所以后面的命令行操作我都是在Window自带的DOS命令行窗口中执行的。

初始化package.json

1.在DOS命令行中进入项目根目录 baiduNews
2.执行npm init,初始化package.json文件

安装依赖

express (使用express来搭建一个简单的Http服务器。当然,你也可以使用node中自带的http模块)
superagent (superagent是node里一个非常方便的、轻量的、渐进式的第三方客户端请求代理模块,用他来请求目标页面)
cheerio (cheerio相当于node版的jQuery,用过jQuery的同学会非常容易上手。它主要是用来获取抓取到的页面元素和其中的数据信息)
// 个人比较喜欢使用yarn来安装依赖包,当然你也可以使用 npm install 来安装依赖,看个人习惯。
yarn add express
yarn add superagent
yarn add cheerio

依赖安装完成后你可以在package.json中查看刚才安装的依赖是否成功。
安装正确后如下图:

安装依赖包

开始coding

一、使用express启动一个简单的本地Http服务器

1、在项目根目录下创建index.js文件(后面都会在这个index文件中进行coding)

2、创建好index.js后,我们首先实例化一个express对象,用它来启动一个本地监听3000端口的Http服务。

const express = require('express');
const app = express();

// ...

let server = app.listen(3000, function () {
  let host = server.address().address;
  let port = server.address().port;
  console.log('Your App is running at http://%s:%s', host, port);
});

对,就是这么简单,不到10行代码,搭建启动一个简单的本地Http服务。

3、按照国际惯例,我们希望在访问本机地址http://localhost:3000的时候,这个服务能给我们犯规一个Hello World!index.js中加入如下代码:

app.get('/', function (req, res) {
  res.send('Hello World!');
});
此时,在DOS中项目根目录baiduNews下执行node index.js,让项目跑起来。之后,打开浏览器,访问http://localhost:3000,你就会发现页面上显示'Hellow World!'字样。
这样,在后面我们获取到百度新闻首页的信息后,就可以在访问http://localhost:3000时看到这些信息。

二、抓取百度新闻首页的新闻信息

1、 首先,我们先来分析一下百度新闻首页的页面信息。
百度新闻首页
百度新闻首页

百度新闻首页大体上分为“热点新闻”、“本地新闻”、“国内新闻”、“国际新闻”......等。这次我们先来尝试抓取左侧“热点新闻”和下方的“本地新闻”两处的新闻数据。

热点新闻DOM结构

F12打开Chrome的控制台,审查页面元素,经过查看左侧“热点新闻”信息所在DOM的结构,我们发现所有的“热点新闻”信息(包括新闻标题和新闻页面链接)都在id#pane-news<div>下面<ul><li>下的<a>标签中。用jQuery的选择器表示为:#pane-news ul li a

2、为了爬取新闻数据,首先我们要用superagent请求目标页面,获取整个新闻首页信息

// 引入所需要的第三方包
const superagent= require('superagent');

let hotNews = [];                                // 热点新闻
let localNews = [];                              // 本地新闻

/**
 * index.js
 * [description] - 使用superagent.get()方法来访问百度新闻首页
 */
superagent.get('http://news.baidu.com/').end((err, res) => {
  if (err) {
    // 如果访问失败或者出错,会这行这里
    console.log(`热点新闻抓取失败 - ${err}`)
  } else {
   // 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res
   // 抓取热点新闻数据
   hotNews = getHotNews(res)
  }
});

3、获取页面信息后,我们来定义一个函数getHotNews()来抓取页面内的“热点新闻”数据。

/**
 * index.js
 * [description] - 抓取热点新闻页面
 */
// 引入所需要的第三方包
const cheerio = require('cheerio');

let getHotNews = (res) => {
  let hotNews = [];
  // 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res.text中。
  
  /* 使用cheerio模块的cherrio.load()方法,将HTMLdocument作为参数传入函数
     以后就可以使用类似jQuery的$(selectior)的方式来获取页面元素
   */
  let $ = cheerio.load(res.text);

  // 找到目标数据所在的页面元素,获取数据
  $('div#pane-news ul li a').each((idx, ele) => {
    // cherrio中$('selector').each()用来遍历所有匹配到的DOM元素
    // 参数idx是当前遍历的元素的索引,ele就是当前便利的DOM元素
    let news = {
      title: $(ele).text(),        // 获取新闻标题
      href: $(ele).attr('href')    // 获取新闻网页链接
    };
    hotNews.push(news)              // 存入最终结果数组
  });
  return hotNews
};

这里要多说几点:

  1. async/await据说是异步编程的终级解决方案,它可以让我们以同步的思维方式来进行异步编程。Promise解决了异步编程的“回调地狱”,async/await同时使异步流程控制变得友好而有清晰,有兴趣的同学可以去了解学习一下,真的很好用。
  2. superagent模块提供了很多比如getpostdelte等方法,可以很方便地进行Ajax请求操作。在请求结束后执行.end()回调函数。.end()接受一个函数作为参数,该函数又有两个参数error和res。当请求失败,error会包含返回的错误信息,请求成功,error值为null,返回的数据会包含在res参数中。
  3. cheerio模块的.load()方法,将HTML document作为参数传入函数,以后就可以使用类似jQuery的$(selectior)的方式来获取页面元素。同时可以使用类似于jQuery中的.each()来遍历元素。此外,还有很多方法,大家可以自行Google/Baidu。

4、将抓取的数据返回给前端浏览器

前面,const app = express();实例化了一个express对象app
app.get('', async() => {})接受两个参数,第一个参数接受一个String类型的路由路径,表示Ajax的请求路径。第二个参数接受一个函数Function,当请求此路径时就会执行这个函数中的代码。
/**
 * [description] - 跟路由
 */
// 当一个get请求 http://localhost:3000时,就会后面的async函数
app.get('/', async (req, res, next) => {
  res.send(hotNews);
});
在DOS中项目根目录baiduNews下执行node index.js,让项目跑起来。之后,打开浏览器,访问http://localhost:3000,你就会发现抓取到的数据返回到了前端页面。我运行代码后浏览器展示的返回信息如下:
注:因为我的Chrome安装了JSONView扩展程序,所以返回的数据在页面展示的时候会被自动格式化为结构性的JSON格式,方便查看。

热点新闻抓取结果

OK!!这样,一个简单的百度“热点新闻”的爬虫就大功告成啦!!

简单总结一下,其实步骤很简单:

  1. express启动一个简单的Http服务
  2. 分析目标页面DOM结构,找到所要抓取的信息的相关DOM元素
  3. 使用superagent请求目标页面
  4. 使用cheerio获取页面元素,获取目标数据
  5. 返回数据到前端浏览器

现在,继续我们的目标,抓取“本地新闻”数据(编码过程中,我们会遇到一些有意思的问题)
有了前面的基础,我们自然而然的会想到利用和上面相同的方法“本地新闻”数据。
1、 分析页面中“本地新闻”部分的DOM结构,如下图:

百度新闻本地新闻

F12打开控制台,审查“本地新闻”DOM元素,我们发现,“本地新闻”分为两个主要部分,“左侧新闻”和右侧的“新闻资讯”。这所有目标数据都在id#local_newsdiv中。“左侧新闻”数据又在id#localnews-focusul标签下的li标签下的a标签中,包括新闻标题和页面链接。“本地资讯”数据又在id#localnews-zixundiv下的ul标签下的li标签下的a标签中,包括新闻标题和页面链接。

2、OK!分析了DOM结构,确定了数据的位置,接下来和爬取“热点新闻”一样,按部就班,定义一个getLocalNews()函数,爬取这些数据。

/**
 * [description] - 抓取本地新闻页面
 */
let getLocalNews = (res) => {
  let localNews = [];
  let $ = cheerio.load(res);
    
  // 本地新闻
  $('ul#localnews-focus li a').each((idx, ele) => {
    let news = {
      title: $(ele).text(),
      href: $(ele).attr('href'),
    };
    localNews.push(news)
  });
    
  // 本地资讯
  $('div#localnews-zixun ul li a').each((index, item) => {
    let news = {
      title: $(item).text(),
      href: $(item).attr('href')
    };
    localNews.push(news);
  });

  return localNews
};

对应的,在superagent.get()中请求页面后,我们需要调用getLocalNews()函数,来爬去本地新闻数据。
superagent.get()函数修改为:

superagent.get('http://news.baidu.com/').end((err, res) => {
  if (err) {
    // 如果访问失败或者出错,会这行这里
    console.log(`热点新闻抓取失败 - ${err}`)
  } else {
   // 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res
   // 抓取热点新闻数据
   hotNews = getHotNews(res)
   localNews = getLocalNews(res)
  }
});

同时,我们要在app.get()路由中也要将数据返回给前端浏览器。app.get()路由代码修改为:

/**
 * [description] - 跟路由
 */
// 当一个get请求 http://localhost:3000时,就会后面的async函数
app.get('/', async (req, res, next) => {
  res.send({
    hotNews: hotNews,
    localNews: localNews
  });
});
编码完成,激动不已!!DOS中让项目跑起来,用浏览器访问http://localhost:3000

尴尬的事情发生了!!返回的数据只有热点新闻,而本地新闻返回一个空数组[ ]。检查代码,发现也没有问题,但为什么一直返回的空数组呢?
经过一番原因查找,才返现问题出在哪里!!

一个有意思的问题

为了找到原因,首先,我们看看用superagent.get('http://news.baidu.com/').end((err, res) => {})请求百度新闻首页在回调函数.end()中的第二个参数res中到底拿到了什么内容?
// 新定义一个全局变量 pageRes
let pageRes = {};        // supergaent页面返回值

// superagent.get()中将res存入pageRes
superagent.get('http://news.baidu.com/').end((err, res) => {
  if (err) {
    // 如果访问失败或者出错,会这行这里
    console.log(`热点新闻抓取失败 - ${err}`)
  } else {
   // 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res
   // 抓取热点新闻数据
   // hotNews = getHotNews(res)
   // localNews = getLocalNews(res)
   pageRes = res
  }
});

// 将pageRes返回给前端浏览器,便于查看
app.get('/', async (req, res, next) => {
  res.send({
    // {}hotNews: hotNews,
    // localNews: localNews,
    pageRes: pageRes
  });
});
访问浏览器http://localhost:3000,页面展示如下内容:

superagent.get()请求返回值

可以看到,返回值中的text字段应该就是整个页面的HTML代码的字符串格式。为了方便我们观察,可以直接把这个text字段值返回给前端浏览器,这样我们就能够清晰地看到经过浏览器渲染后的页面。

修改给前端浏览器的返回值

app.get('/', async (req, res, next) => {
  res.send(pageRes.text)
}

访问浏览器http://localhost:3000,页面展示如下内容:

本地新闻返回页面

审查元素才发现,原来我们抓取的目标数据所在的DOM元素中是空的,里面没有数据!
到这里,一切水落石出!在我们使用superagent.get()访问百度新闻首页时,res中包含的获取的页面内容中,我们想要的“本地新闻”数据还没有生成,DOM节点元素是空的,所以出现前面的情况!抓取后返回的数据一直是空数组[ ]

本地新闻请求接口

在控制台的Network中我们发现页面请求了一次这样的接口:
http://localhost:3000/widget?id=LocalNews&ajax=json&t=1526295667917,接口状态 404
这应该就是百度新闻获取“本地新闻”的接口,到这里一切都明白了!“本地新闻”是在页面加载后动态请求上面这个接口获取的,所以我们用superagent.get()请求的页面再去请求这个接口时,接口URLhostname部分变成了本地IP地址,而本机上没有这个接口,所以404,请求不到数据。

找到原因,我们来想办法解决这个问题!!

  1. 直接使用superagent访问正确合法的百度“本地新闻”的接口,获取数据后返回给前端浏览器。
  2. 使用第三方npm包,模拟浏览器访问百度新闻首页,在这个模拟浏览器中当“本地新闻”加载成功后,抓取数据,返回给前端浏览器。

以上方法均可,我们来试试比较有意思的第二种方法

使用Nightmare自动化测试工具

Electron可以让你使用纯JavaScript调用Chrome丰富的原生的接口来创造桌面应用。你可以把它看作一个专注于桌面应用的Node.js的变体,而不是Web服务器。其基于浏览器的应用方式可以极方便的做各种响应式的交互

Nightmare是一个基于Electron的框架,针对Web自动化测试和爬虫,因为其具有跟PlantomJS一样的自动化测试的功能可以在页面上模拟用户的行为触发一些异步数据加载,也可以跟Request库一样直接访问URL来抓取数据,并且可以设置页面的延迟时间,所以无论是手动触发脚本还是行为触发脚本都是轻而易举的。

安装依赖

// 安装nightmare
yarn add nightmare

为获取“本地新闻”,继续coding...

index.js中新增如下代码:

const Nightmare = require('nightmare');          // 自动化测试包,处理动态页面
const nightmare = Nightmare({ show: true });     // show:true  显示内置模拟浏览器

/**
 * [description] - 抓取本地新闻页面
 * [nremark] - 百度本地新闻在访问页面后加载js定位IP位置后获取对应新闻,
 * 所以抓取本地新闻需要使用 nightmare 一类的自动化测试工具,
 * 模拟浏览器环境访问页面,使js运行,生成动态页面再抓取
 */
// 抓取本地新闻页面
nightmare
.goto('http://news.baidu.com/')
.wait("div#local_news")
.evaluate(() => document.querySelector("div#local_news").innerHTML)
.then(htmlStr => {
  // 获取本地新闻数据
  localNews = getLocalNews(htmlStr)
})
.catch(error => {
  console.log(`本地新闻抓取失败 - ${error}`);
})

修改getLocalNews()函数为:

/**
 * [description]- 获取本地新闻数据
 */
let getLocalNews = (htmlStr) => {
  let localNews = [];
  let $ = cheerio.load(htmlStr);

  // 本地新闻
  $('ul#localnews-focus li a').each((idx, ele) => {
    let news = {
      title: $(ele).text(),
      href: $(ele).attr('href'),
    };
    localNews.push(news)
  });

  // 本地资讯
  $('div#localnews-zixun ul li a').each((index, item) => {
    let news = {
      title: $(item).text(),
      href: $(item).attr('href')
    };
    localNews.push(news);
  });

  return localNews
}

修改app.get('/')路由为:

/**
 * [description] - 跟路由
 */
// 当一个get请求 http://localhost:3000时,就会后面的async函数
app.get('/', async (req, res, next) => {
  res.send({
    hotNews: hotNews,
    localNews: localNews
  })
});
此时,DOS命令行中重新让项目跑起来,浏览器访问https://localhost:3000,看看页面展示的信息,看是否抓取到了“本地新闻”数据!

至此,一个简单而又完整的抓取百度新闻页面“热点新闻”和“本地新闻”的爬虫就大功告成啦!!

最后总结一下,整体思路如下:

  1. express启动一个简单的Http服务
  2. 分析目标页面DOM结构,找到所要抓取的信息的相关DOM元
  3. 使用superagent请求目标页面
  4. 动态页面(需要加载页面后运行JS或请求接口的页面)可以使用Nightmare模拟浏览器访问
  5. 使用cheerio获取页面元素,获取目标数据

完整代码

爬虫完整代码GitHub地址:完整代码

后面,应该还会做一些进阶,来爬取某些网站上比较好看的图片(手动滑稽),会牵扯到并发控制反-反爬虫的一些策略。再用爬虫取爬去一些需要登录和输入验证码的网站,欢迎到时大家关注和指正交流。

我想说

再次感谢大家的点赞和关注和评论,谢谢大家的支持,谢谢!我自己觉得我算是一个爱文字,爱音乐,同时也喜欢coding的半文艺程序员。之前也一直想着写一写技术性和其他偏文学性的文章。虽然自己的底子没有多么优秀,但总是觉得在写文章的过程中,不论是技术性的还是偏文学性的,这个过程中可以督促自己去思考,督促自己去学习和交流。毕竟每天忙忙碌碌之余,还是要活出自己不一样的生活。所以,以后如果有一些好的文章我会积极和大家分享!再次感谢大家的支持!
查看原文

怀瑾以南辞 赞了文章 · 2020-11-05

分分钟教你用node.js写个爬虫

分分钟教你用node.js写个爬虫

写在前面

十分感谢大家的点赞和关注。其实,这是我第一次在segmentfault上写文章。因为我也是前段时间偶然之间才开始了解和学习爬虫,而且学习node的时间也不是很长。虽然用node做过一些后端的项目,但其实在node和爬虫方面我还是一个新人,这篇文章主要是想和大家分享一下node和爬虫方面的基本知识,希望对大家有帮助,也想和大家一起交流,一起学习,再次谢谢大家的支持!

对了,我开通了个人的 个人主页 ,里面有自己的技术文章,还会有个人的随想、思考和日志。以后所有的文章都会第一时间更新到这里,然后同步到其他平台。有喜欢的朋友可以没事去逛逛,再次感谢大家的支持!

一、什么是爬虫

网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。
WIKIPEDIA 爬虫介绍

二、爬虫的分类

  • 通用网络爬虫(全网爬虫)
爬行对象从一些 种子URL 扩充到整个 Web,主要为门户站点搜索引擎和大型 Web 服务提供商采集数据。

通用爬虫工作流程

  • 聚焦网络爬虫(主题网络爬虫)
指选择性 地爬行那些与预先定义好的主题相关页面的网络爬虫。
  • 增量式网络爬虫
指对已下载网页采取增量式更新和 只爬行新产生的或者已经发生变化网页 的爬虫,它能够在一定程度上保证所爬行的页面是尽可能新的页面。
  • Deep Web 爬虫
爬行对象是一些在用户填入关键字搜索或登录后才能访问到的深层网页信息的爬虫。

三、爬虫的爬行策略

  • 通用网络爬虫(全网爬虫)
深度优先策略、广度优先策略

页面关系模拟树状结构

  • 聚焦网络爬虫(主题网络爬虫)
基于内容评价的爬行策略(内容相关性),基于链接结构评价的爬行策略、基于增强学习的爬行策略(链接重要性),基于语境图的爬行策略(距离,图论中两节点间边的权重)
  • 增量式网络爬虫
统一更新法、个体更新法、基于分类的更新法、自适应调频更新法
  • Deep Web 爬虫
Deep Web 爬虫爬行过程中最重要部分就是表单填写,包含两种类型:基于领域知识的表单填写、基于网页结构分析的表单填写

现代的网页爬虫的行为通常是四种策略组合的结果:

选择策略:决定所要下载的页面;
重新访问策略:决定什么时候检查页面的更新变化;
平衡礼貌策略:指出怎样避免站点超载;
并行策略:指出怎么协同达到分布式抓取的效果;

现代分布式爬虫系统

四、写一个简单网页爬虫的流程

  1. 确定爬取对象(网站/页面)
  2. 分析页面内容(目标数据/DOM结构)
  3. 确定开发语言、框架、工具等
  4. 编码 测试,爬取数据
  5. 优化

一个简单的百度新闻爬虫

确定爬取对象(网站/页面)

百度新闻http://news.baidu.com/

分析页面内容(目标数据/DOM结构)

······

确定开发语言、框架、工具等

node.js (express) + SublimeText 3

编码,测试,爬取数据

coding ···

Let's start

新建项目目录

1.在合适的磁盘目录下创建项目目录baiduNews(我的项目目录是:F:\web\baiduNews

注:因为在写这篇文章的时候用的电脑真心比较渣。安装WebStorm或者VsCode跑项目有些吃力。所以后面的命令行操作我都是在Window自带的DOS命令行窗口中执行的。

初始化package.json

1.在DOS命令行中进入项目根目录 baiduNews
2.执行npm init,初始化package.json文件

安装依赖

express (使用express来搭建一个简单的Http服务器。当然,你也可以使用node中自带的http模块)
superagent (superagent是node里一个非常方便的、轻量的、渐进式的第三方客户端请求代理模块,用他来请求目标页面)
cheerio (cheerio相当于node版的jQuery,用过jQuery的同学会非常容易上手。它主要是用来获取抓取到的页面元素和其中的数据信息)
// 个人比较喜欢使用yarn来安装依赖包,当然你也可以使用 npm install 来安装依赖,看个人习惯。
yarn add express
yarn add superagent
yarn add cheerio

依赖安装完成后你可以在package.json中查看刚才安装的依赖是否成功。
安装正确后如下图:

安装依赖包

开始coding

一、使用express启动一个简单的本地Http服务器

1、在项目根目录下创建index.js文件(后面都会在这个index文件中进行coding)

2、创建好index.js后,我们首先实例化一个express对象,用它来启动一个本地监听3000端口的Http服务。

const express = require('express');
const app = express();

// ...

let server = app.listen(3000, function () {
  let host = server.address().address;
  let port = server.address().port;
  console.log('Your App is running at http://%s:%s', host, port);
});

对,就是这么简单,不到10行代码,搭建启动一个简单的本地Http服务。

3、按照国际惯例,我们希望在访问本机地址http://localhost:3000的时候,这个服务能给我们犯规一个Hello World!index.js中加入如下代码:

app.get('/', function (req, res) {
  res.send('Hello World!');
});
此时,在DOS中项目根目录baiduNews下执行node index.js,让项目跑起来。之后,打开浏览器,访问http://localhost:3000,你就会发现页面上显示'Hellow World!'字样。
这样,在后面我们获取到百度新闻首页的信息后,就可以在访问http://localhost:3000时看到这些信息。

二、抓取百度新闻首页的新闻信息

1、 首先,我们先来分析一下百度新闻首页的页面信息。
百度新闻首页
百度新闻首页

百度新闻首页大体上分为“热点新闻”、“本地新闻”、“国内新闻”、“国际新闻”......等。这次我们先来尝试抓取左侧“热点新闻”和下方的“本地新闻”两处的新闻数据。

热点新闻DOM结构

F12打开Chrome的控制台,审查页面元素,经过查看左侧“热点新闻”信息所在DOM的结构,我们发现所有的“热点新闻”信息(包括新闻标题和新闻页面链接)都在id#pane-news<div>下面<ul><li>下的<a>标签中。用jQuery的选择器表示为:#pane-news ul li a

2、为了爬取新闻数据,首先我们要用superagent请求目标页面,获取整个新闻首页信息

// 引入所需要的第三方包
const superagent= require('superagent');

let hotNews = [];                                // 热点新闻
let localNews = [];                              // 本地新闻

/**
 * index.js
 * [description] - 使用superagent.get()方法来访问百度新闻首页
 */
superagent.get('http://news.baidu.com/').end((err, res) => {
  if (err) {
    // 如果访问失败或者出错,会这行这里
    console.log(`热点新闻抓取失败 - ${err}`)
  } else {
   // 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res
   // 抓取热点新闻数据
   hotNews = getHotNews(res)
  }
});

3、获取页面信息后,我们来定义一个函数getHotNews()来抓取页面内的“热点新闻”数据。

/**
 * index.js
 * [description] - 抓取热点新闻页面
 */
// 引入所需要的第三方包
const cheerio = require('cheerio');

let getHotNews = (res) => {
  let hotNews = [];
  // 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res.text中。
  
  /* 使用cheerio模块的cherrio.load()方法,将HTMLdocument作为参数传入函数
     以后就可以使用类似jQuery的$(selectior)的方式来获取页面元素
   */
  let $ = cheerio.load(res.text);

  // 找到目标数据所在的页面元素,获取数据
  $('div#pane-news ul li a').each((idx, ele) => {
    // cherrio中$('selector').each()用来遍历所有匹配到的DOM元素
    // 参数idx是当前遍历的元素的索引,ele就是当前便利的DOM元素
    let news = {
      title: $(ele).text(),        // 获取新闻标题
      href: $(ele).attr('href')    // 获取新闻网页链接
    };
    hotNews.push(news)              // 存入最终结果数组
  });
  return hotNews
};

这里要多说几点:

  1. async/await据说是异步编程的终级解决方案,它可以让我们以同步的思维方式来进行异步编程。Promise解决了异步编程的“回调地狱”,async/await同时使异步流程控制变得友好而有清晰,有兴趣的同学可以去了解学习一下,真的很好用。
  2. superagent模块提供了很多比如getpostdelte等方法,可以很方便地进行Ajax请求操作。在请求结束后执行.end()回调函数。.end()接受一个函数作为参数,该函数又有两个参数error和res。当请求失败,error会包含返回的错误信息,请求成功,error值为null,返回的数据会包含在res参数中。
  3. cheerio模块的.load()方法,将HTML document作为参数传入函数,以后就可以使用类似jQuery的$(selectior)的方式来获取页面元素。同时可以使用类似于jQuery中的.each()来遍历元素。此外,还有很多方法,大家可以自行Google/Baidu。

4、将抓取的数据返回给前端浏览器

前面,const app = express();实例化了一个express对象app
app.get('', async() => {})接受两个参数,第一个参数接受一个String类型的路由路径,表示Ajax的请求路径。第二个参数接受一个函数Function,当请求此路径时就会执行这个函数中的代码。
/**
 * [description] - 跟路由
 */
// 当一个get请求 http://localhost:3000时,就会后面的async函数
app.get('/', async (req, res, next) => {
  res.send(hotNews);
});
在DOS中项目根目录baiduNews下执行node index.js,让项目跑起来。之后,打开浏览器,访问http://localhost:3000,你就会发现抓取到的数据返回到了前端页面。我运行代码后浏览器展示的返回信息如下:
注:因为我的Chrome安装了JSONView扩展程序,所以返回的数据在页面展示的时候会被自动格式化为结构性的JSON格式,方便查看。

热点新闻抓取结果

OK!!这样,一个简单的百度“热点新闻”的爬虫就大功告成啦!!

简单总结一下,其实步骤很简单:

  1. express启动一个简单的Http服务
  2. 分析目标页面DOM结构,找到所要抓取的信息的相关DOM元素
  3. 使用superagent请求目标页面
  4. 使用cheerio获取页面元素,获取目标数据
  5. 返回数据到前端浏览器

现在,继续我们的目标,抓取“本地新闻”数据(编码过程中,我们会遇到一些有意思的问题)
有了前面的基础,我们自然而然的会想到利用和上面相同的方法“本地新闻”数据。
1、 分析页面中“本地新闻”部分的DOM结构,如下图:

百度新闻本地新闻

F12打开控制台,审查“本地新闻”DOM元素,我们发现,“本地新闻”分为两个主要部分,“左侧新闻”和右侧的“新闻资讯”。这所有目标数据都在id#local_newsdiv中。“左侧新闻”数据又在id#localnews-focusul标签下的li标签下的a标签中,包括新闻标题和页面链接。“本地资讯”数据又在id#localnews-zixundiv下的ul标签下的li标签下的a标签中,包括新闻标题和页面链接。

2、OK!分析了DOM结构,确定了数据的位置,接下来和爬取“热点新闻”一样,按部就班,定义一个getLocalNews()函数,爬取这些数据。

/**
 * [description] - 抓取本地新闻页面
 */
let getLocalNews = (res) => {
  let localNews = [];
  let $ = cheerio.load(res);
    
  // 本地新闻
  $('ul#localnews-focus li a').each((idx, ele) => {
    let news = {
      title: $(ele).text(),
      href: $(ele).attr('href'),
    };
    localNews.push(news)
  });
    
  // 本地资讯
  $('div#localnews-zixun ul li a').each((index, item) => {
    let news = {
      title: $(item).text(),
      href: $(item).attr('href')
    };
    localNews.push(news);
  });

  return localNews
};

对应的,在superagent.get()中请求页面后,我们需要调用getLocalNews()函数,来爬去本地新闻数据。
superagent.get()函数修改为:

superagent.get('http://news.baidu.com/').end((err, res) => {
  if (err) {
    // 如果访问失败或者出错,会这行这里
    console.log(`热点新闻抓取失败 - ${err}`)
  } else {
   // 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res
   // 抓取热点新闻数据
   hotNews = getHotNews(res)
   localNews = getLocalNews(res)
  }
});

同时,我们要在app.get()路由中也要将数据返回给前端浏览器。app.get()路由代码修改为:

/**
 * [description] - 跟路由
 */
// 当一个get请求 http://localhost:3000时,就会后面的async函数
app.get('/', async (req, res, next) => {
  res.send({
    hotNews: hotNews,
    localNews: localNews
  });
});
编码完成,激动不已!!DOS中让项目跑起来,用浏览器访问http://localhost:3000

尴尬的事情发生了!!返回的数据只有热点新闻,而本地新闻返回一个空数组[ ]。检查代码,发现也没有问题,但为什么一直返回的空数组呢?
经过一番原因查找,才返现问题出在哪里!!

一个有意思的问题

为了找到原因,首先,我们看看用superagent.get('http://news.baidu.com/').end((err, res) => {})请求百度新闻首页在回调函数.end()中的第二个参数res中到底拿到了什么内容?
// 新定义一个全局变量 pageRes
let pageRes = {};        // supergaent页面返回值

// superagent.get()中将res存入pageRes
superagent.get('http://news.baidu.com/').end((err, res) => {
  if (err) {
    // 如果访问失败或者出错,会这行这里
    console.log(`热点新闻抓取失败 - ${err}`)
  } else {
   // 访问成功,请求http://news.baidu.com/页面所返回的数据会包含在res
   // 抓取热点新闻数据
   // hotNews = getHotNews(res)
   // localNews = getLocalNews(res)
   pageRes = res
  }
});

// 将pageRes返回给前端浏览器,便于查看
app.get('/', async (req, res, next) => {
  res.send({
    // {}hotNews: hotNews,
    // localNews: localNews,
    pageRes: pageRes
  });
});
访问浏览器http://localhost:3000,页面展示如下内容:

superagent.get()请求返回值

可以看到,返回值中的text字段应该就是整个页面的HTML代码的字符串格式。为了方便我们观察,可以直接把这个text字段值返回给前端浏览器,这样我们就能够清晰地看到经过浏览器渲染后的页面。

修改给前端浏览器的返回值

app.get('/', async (req, res, next) => {
  res.send(pageRes.text)
}

访问浏览器http://localhost:3000,页面展示如下内容:

本地新闻返回页面

审查元素才发现,原来我们抓取的目标数据所在的DOM元素中是空的,里面没有数据!
到这里,一切水落石出!在我们使用superagent.get()访问百度新闻首页时,res中包含的获取的页面内容中,我们想要的“本地新闻”数据还没有生成,DOM节点元素是空的,所以出现前面的情况!抓取后返回的数据一直是空数组[ ]

本地新闻请求接口

在控制台的Network中我们发现页面请求了一次这样的接口:
http://localhost:3000/widget?id=LocalNews&ajax=json&t=1526295667917,接口状态 404
这应该就是百度新闻获取“本地新闻”的接口,到这里一切都明白了!“本地新闻”是在页面加载后动态请求上面这个接口获取的,所以我们用superagent.get()请求的页面再去请求这个接口时,接口URLhostname部分变成了本地IP地址,而本机上没有这个接口,所以404,请求不到数据。

找到原因,我们来想办法解决这个问题!!

  1. 直接使用superagent访问正确合法的百度“本地新闻”的接口,获取数据后返回给前端浏览器。
  2. 使用第三方npm包,模拟浏览器访问百度新闻首页,在这个模拟浏览器中当“本地新闻”加载成功后,抓取数据,返回给前端浏览器。

以上方法均可,我们来试试比较有意思的第二种方法

使用Nightmare自动化测试工具

Electron可以让你使用纯JavaScript调用Chrome丰富的原生的接口来创造桌面应用。你可以把它看作一个专注于桌面应用的Node.js的变体,而不是Web服务器。其基于浏览器的应用方式可以极方便的做各种响应式的交互

Nightmare是一个基于Electron的框架,针对Web自动化测试和爬虫,因为其具有跟PlantomJS一样的自动化测试的功能可以在页面上模拟用户的行为触发一些异步数据加载,也可以跟Request库一样直接访问URL来抓取数据,并且可以设置页面的延迟时间,所以无论是手动触发脚本还是行为触发脚本都是轻而易举的。

安装依赖

// 安装nightmare
yarn add nightmare

为获取“本地新闻”,继续coding...

index.js中新增如下代码:

const Nightmare = require('nightmare');          // 自动化测试包,处理动态页面
const nightmare = Nightmare({ show: true });     // show:true  显示内置模拟浏览器

/**
 * [description] - 抓取本地新闻页面
 * [nremark] - 百度本地新闻在访问页面后加载js定位IP位置后获取对应新闻,
 * 所以抓取本地新闻需要使用 nightmare 一类的自动化测试工具,
 * 模拟浏览器环境访问页面,使js运行,生成动态页面再抓取
 */
// 抓取本地新闻页面
nightmare
.goto('http://news.baidu.com/')
.wait("div#local_news")
.evaluate(() => document.querySelector("div#local_news").innerHTML)
.then(htmlStr => {
  // 获取本地新闻数据
  localNews = getLocalNews(htmlStr)
})
.catch(error => {
  console.log(`本地新闻抓取失败 - ${error}`);
})

修改getLocalNews()函数为:

/**
 * [description]- 获取本地新闻数据
 */
let getLocalNews = (htmlStr) => {
  let localNews = [];
  let $ = cheerio.load(htmlStr);

  // 本地新闻
  $('ul#localnews-focus li a').each((idx, ele) => {
    let news = {
      title: $(ele).text(),
      href: $(ele).attr('href'),
    };
    localNews.push(news)
  });

  // 本地资讯
  $('div#localnews-zixun ul li a').each((index, item) => {
    let news = {
      title: $(item).text(),
      href: $(item).attr('href')
    };
    localNews.push(news);
  });

  return localNews
}

修改app.get('/')路由为:

/**
 * [description] - 跟路由
 */
// 当一个get请求 http://localhost:3000时,就会后面的async函数
app.get('/', async (req, res, next) => {
  res.send({
    hotNews: hotNews,
    localNews: localNews
  })
});
此时,DOS命令行中重新让项目跑起来,浏览器访问https://localhost:3000,看看页面展示的信息,看是否抓取到了“本地新闻”数据!

至此,一个简单而又完整的抓取百度新闻页面“热点新闻”和“本地新闻”的爬虫就大功告成啦!!

最后总结一下,整体思路如下:

  1. express启动一个简单的Http服务
  2. 分析目标页面DOM结构,找到所要抓取的信息的相关DOM元
  3. 使用superagent请求目标页面
  4. 动态页面(需要加载页面后运行JS或请求接口的页面)可以使用Nightmare模拟浏览器访问
  5. 使用cheerio获取页面元素,获取目标数据

完整代码

爬虫完整代码GitHub地址:完整代码

后面,应该还会做一些进阶,来爬取某些网站上比较好看的图片(手动滑稽),会牵扯到并发控制反-反爬虫的一些策略。再用爬虫取爬去一些需要登录和输入验证码的网站,欢迎到时大家关注和指正交流。

我想说

再次感谢大家的点赞和关注和评论,谢谢大家的支持,谢谢!我自己觉得我算是一个爱文字,爱音乐,同时也喜欢coding的半文艺程序员。之前也一直想着写一写技术性和其他偏文学性的文章。虽然自己的底子没有多么优秀,但总是觉得在写文章的过程中,不论是技术性的还是偏文学性的,这个过程中可以督促自己去思考,督促自己去学习和交流。毕竟每天忙忙碌碌之余,还是要活出自己不一样的生活。所以,以后如果有一些好的文章我会积极和大家分享!再次感谢大家的支持!
查看原文

赞 56 收藏 36 评论 19

怀瑾以南辞 收藏了文章 · 2020-11-03

vue和微信小程序的区别、比较

写了vue项目和小程序,发现二者有许多相同之处,在此想总结一下二者的共同点和区别。

一、生命周期

先贴两张图:

vue生命周期

clipboard.png

小程序生命周期

clipboard.png

相比之下,小程序的钩子函数要简单得多。

vue的钩子函数在跳转新页面时,钩子函数都会触发,但是小程序的钩子函数,页面不同的跳转方式,触发的钩子并不一样。

  • onLoad: 页面加载
    一个页面只会调用一次,可以在 onLoad 中获取打开当前页面所调用的 query 参数。
  • onShow: 页面显示
    每次打开页面都会调用一次。
  • onReady: 页面初次渲染完成
    一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。
    对界面的设置如wx.setNavigationBarTitle请在onReady之后设置。详见生命周期
  • onHide: 页面隐藏
    navigateTo或底部tab切换时调用。
  • onUnload: 页面卸载
    redirectTonavigateBack的时候调用。

数据请求

在页面加载请求数据时,两者钩子的使用有些类似,vue一般会在created或者mounted中请求数据,而在小程序,会在onLoad或者onShow中请求数据。

二、数据绑定

VUE:vue动态绑定一个变量的值为元素的某个属性的时候,会在变量前面加上冒号:,例:

<img :data-original="imgSrc"/>

小程序:绑定某个变量的值为元素属性时,会用两个大括号括起来,如果不加括号,为被认为是字符串。例:

<image data-original="{{imgSrc}}"></image>

三、列表渲染

直接贴代码,两者还是有些相似
vue:

<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>

var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

小程序:


Page({
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

<text wx:for="{{items}}">{{item}}</text>

四、显示与隐藏元素

vue中,使用v-ifv-show控制元素的显示和隐藏

小程序中,使用wx-ifhidden控制元素的显示和隐藏

五、事件处理

vue:使用v-on:event绑定事件,或者使用@event绑定事件,例如:

<button v-on:click="counter += 1">Add 1</button>
<button v-on:click.stop="counter+=1">Add1</button>  //阻止事件冒泡

小程序中,全用bindtap(bind+event),或者catchtap(catch+event)绑定事件,例如:

<button bindtap="noWork">明天不上班</button>
<button catchtap="noWork">明天不上班</button>  //阻止事件冒泡

六、数据双向绑定

1.设置值

vue中,只需要再表单元素上加上v-model,然后再绑定data中对应的一个值,当表单元素内容发生变化时,data中对应的值也会相应改变,这是vue非常nice的一点。

<div id="app">
    <input v-model="reason" placeholder="填写理由" class='reason'/>
</div>

new Vue({
  el: '#app',
  data: {
   reason:''
  }
})

但是在小程序中,却没有这个功能。那怎么办呢?
当表单内容发生变化时,会触发表单元素上绑定的方法,然后在该方法中,通过this.setData({key:value})来将表单上的值赋值给data中的对应值。
下面是代码,可以感受一下:

<input bindinput="bindReason" placeholder="填写理由" class='reason' value='{{reason}}' name="reason" />

Page({
data:{
    reason:''
},
bindReason(e) {
    this.setData({
      reason: e.detail.value
    })
  }
})

当页面表单元素很多的时候,更改值就是一件体力活了。和小程序一比较,vuev-model简直爽的不要不要的。

2.取值

vue中,通过this.reason取值

小程序中,通过this.data.reason取值

clipboard.png

七、绑定事件传参

vue中,绑定事件传参挺简单,只需要在触发事件的方法中,把需要传递的数据作为形参传入就可以了,例如:

<button @click="say('明天不上班')"></button>

new Vue({
  el: '#app',
  methods:{
    say(arg){
    consloe.log(arg)
    }
  }
})

小程序中,不能直接在绑定事件的方法中传入参数,需要将参数作为属性值,绑定到元素上的data-属性上,然后在方法中,通过e.currentTarget.dataset.*的方式获取,从而完成参数的传递,很麻烦有没有...

<view class='tr' bindtap='toApprove' data-id="{{item.id}}"></view>
Page({
data:{
    reason:''
},
toApprove(e) {
    let id = e.currentTarget.dataset.id;
  }
})

clipboard.png

八、父子组件通信

1.子组件的使用

vue中,需要:

  1. 编写子组件
  2. 在需要使用的父组件中通过import引入
  3. vuecomponents中注册
  4. 在模板中使用
//子组件 bar.vue
<template>
  <div class="search-box">
    <div @click="say" :title="title" class="icon-dismiss"></div>
  </div>
</template>
<script>
export default{
props:{
    title:{
       type:String,
       default:''
      }
    }
},
methods:{
    say(){
       console.log('明天不上班');
       this.$emit('helloWorld')
    }
}
</script>

// 父组件 foo.vue
<template>
  <div class="container">
    <bar :title="title" @helloWorld="helloWorld"></bar>
  </div>
</template>

<script>
import Bar from './bar.vue'
export default{
data(){
    return{
        title:"我是标题"
    }
},
methods:{
    helloWorld(){
        console.log('我接收到子组件传递的事件了')
    }
},
components:{
    Bar
}
</script>

小程序中,需要:

  1. 编写子组件
  2. 在子组件的json文件中,将该文件声明为组件

    {
      "component": true
    }
  3. 在需要引入的父组件的json文件中,在usingComponents填写引入组件的组件名以及路径

    "usingComponents": {
        "tab-bar": "../../components/tabBar/tabBar"
      }
  4. 在父组件中,直接引入即可

    <tab-bar currentpage="index"></tab-bar>

    具体代码:

    // 子组件
    <!--components/tabBar/tabBar.wxml-->
    <view class='tabbar-wrapper'>
      <view class='left-bar {{currentpage==="index"?"active":""}}' bindtap='jumpToIndex'>
        <text class='iconfont icon-shouye'></text>
        <view>首页</view>
      </view>
      <view class='right-bar {{currentpage==="setting"?"active":""}}' bindtap='jumpToSetting'>
        <text class='iconfont icon-shezhi'></text>
        <view>设置</view>
      </view>
    </view>
    

2.父子组件间通信

vue

父组件向子组件传递数据,只需要在父组件通过v-bind传入一个值,在子组件中,通过props接收,即可完成数据的传递,示例:

// 父组件 foo.vue
<template>
  <div class="container">
    <bar :title="title"></bar>
  </div>
</template>
<script>
import Bar from './bar.vue'
export default{
data(){
    return{        
        title:"我是标题"
    }
},
components:{
    Bar
}
</script>

// 子组件bar.vue
<template>
  <div class="search-box">
    <div :title="title" ></div>
  </div>
</template>
<script>
export default{
props:{
    title:{
       type:String,
       default:''
      }
    }
}
</script>

子组件和父组件通信可以通过this.$emit将方法和数据传递给父组件。

小程序

父组件向子组件通信和vue类似,但是小程序没有通过v-bind,而是直接将值赋值给一个变量,如下:

<tab-bar currentpage="index"></tab-bar>

此处, “index”就是要向子组件传递的值

在子组件properties中,接收传递的值

properties: {
    // 弹窗标题
    currentpage: {            // 属性名
      type: String,     // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
      value: 'index'     // 属性初始值(可选),如果未指定则会根据类型选择一个
    }
  }

子组件向父组件通信和vue也很类似,代码如下:

//子组件中
methods: {   
    // 传递给父组件
    cancelBut: function (e) {
      var that = this;
      var myEventDetail = { pickerShow: false, type: 'cancel' } // detail对象,提供给事件监听函数
      this.triggerEvent('myevent', myEventDetail) //myevent自定义名称事件,父组件中使用
    },
}

//父组件中
<bar bind:myevent="toggleToast"></bar>

// 获取子组件信息
toggleToast(e){
    console.log(e.detail)
}

如果父组件想要调用子组件的方法

vue会给子组件添加一个ref属性,通过this.$refs.ref的值便可以获取到该子组件,然后便可以调用子组件中的任意方法,例如:

//子组件
<bar ref="bar"></bar>

//父组件
this.$ref.bar.子组件的方法

小程序是给子组件添加id或者class,然后通过this.selectComponent找到子组件,然后再调用子组件的方法,示例:

//子组件
<bar id="bar"></bar>

// 父组件
this.selectComponent('#id').syaHello()

小程序父组件改变子组件样式

1.父组件将style传入子组件
2.父组件传入变量控制子组件样式
3.在父组件样式中,在子组件类名前面加上父组件类名

<view class='share-button-container' bindtap='handleShareBtn'>
   <share-button  product="{{goodProduct}}" type="1" back-color="#fff" fore-color="#9e292f" bind:error="on_error" />
</view>

.share-button-container .button--btn-navigator__hover{
  background: #fff;
}

小程序和vue在这点上太相似了,有木有。。。
clipboard.png

九、废话

还有好多地方没写,之后再慢慢加上、精简。感觉自己写的有点冗余,大佬勿喷!!!
如果觉得有帮助,希望帮忙点个赞和收藏

clipboard.png

查看原文

怀瑾以南辞 赞了文章 · 2020-11-03

vue和微信小程序的区别、比较

写了vue项目和小程序,发现二者有许多相同之处,在此想总结一下二者的共同点和区别。

一、生命周期

先贴两张图:

vue生命周期

clipboard.png

小程序生命周期

clipboard.png

相比之下,小程序的钩子函数要简单得多。

vue的钩子函数在跳转新页面时,钩子函数都会触发,但是小程序的钩子函数,页面不同的跳转方式,触发的钩子并不一样。

  • onLoad: 页面加载
    一个页面只会调用一次,可以在 onLoad 中获取打开当前页面所调用的 query 参数。
  • onShow: 页面显示
    每次打开页面都会调用一次。
  • onReady: 页面初次渲染完成
    一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。
    对界面的设置如wx.setNavigationBarTitle请在onReady之后设置。详见生命周期
  • onHide: 页面隐藏
    navigateTo或底部tab切换时调用。
  • onUnload: 页面卸载
    redirectTonavigateBack的时候调用。

数据请求

在页面加载请求数据时,两者钩子的使用有些类似,vue一般会在created或者mounted中请求数据,而在小程序,会在onLoad或者onShow中请求数据。

二、数据绑定

VUE:vue动态绑定一个变量的值为元素的某个属性的时候,会在变量前面加上冒号:,例:

<img :data-original="imgSrc"/>

小程序:绑定某个变量的值为元素属性时,会用两个大括号括起来,如果不加括号,为被认为是字符串。例:

<image data-original="{{imgSrc}}"></image>

三、列表渲染

直接贴代码,两者还是有些相似
vue:

<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>

var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

小程序:


Page({
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

<text wx:for="{{items}}">{{item}}</text>

四、显示与隐藏元素

vue中,使用v-ifv-show控制元素的显示和隐藏

小程序中,使用wx-ifhidden控制元素的显示和隐藏

五、事件处理

vue:使用v-on:event绑定事件,或者使用@event绑定事件,例如:

<button v-on:click="counter += 1">Add 1</button>
<button v-on:click.stop="counter+=1">Add1</button>  //阻止事件冒泡

小程序中,全用bindtap(bind+event),或者catchtap(catch+event)绑定事件,例如:

<button bindtap="noWork">明天不上班</button>
<button catchtap="noWork">明天不上班</button>  //阻止事件冒泡

六、数据双向绑定

1.设置值

vue中,只需要再表单元素上加上v-model,然后再绑定data中对应的一个值,当表单元素内容发生变化时,data中对应的值也会相应改变,这是vue非常nice的一点。

<div id="app">
    <input v-model="reason" placeholder="填写理由" class='reason'/>
</div>

new Vue({
  el: '#app',
  data: {
   reason:''
  }
})

但是在小程序中,却没有这个功能。那怎么办呢?
当表单内容发生变化时,会触发表单元素上绑定的方法,然后在该方法中,通过this.setData({key:value})来将表单上的值赋值给data中的对应值。
下面是代码,可以感受一下:

<input bindinput="bindReason" placeholder="填写理由" class='reason' value='{{reason}}' name="reason" />

Page({
data:{
    reason:''
},
bindReason(e) {
    this.setData({
      reason: e.detail.value
    })
  }
})

当页面表单元素很多的时候,更改值就是一件体力活了。和小程序一比较,vuev-model简直爽的不要不要的。

2.取值

vue中,通过this.reason取值

小程序中,通过this.data.reason取值

clipboard.png

七、绑定事件传参

vue中,绑定事件传参挺简单,只需要在触发事件的方法中,把需要传递的数据作为形参传入就可以了,例如:

<button @click="say('明天不上班')"></button>

new Vue({
  el: '#app',
  methods:{
    say(arg){
    consloe.log(arg)
    }
  }
})

小程序中,不能直接在绑定事件的方法中传入参数,需要将参数作为属性值,绑定到元素上的data-属性上,然后在方法中,通过e.currentTarget.dataset.*的方式获取,从而完成参数的传递,很麻烦有没有...

<view class='tr' bindtap='toApprove' data-id="{{item.id}}"></view>
Page({
data:{
    reason:''
},
toApprove(e) {
    let id = e.currentTarget.dataset.id;
  }
})

clipboard.png

八、父子组件通信

1.子组件的使用

vue中,需要:

  1. 编写子组件
  2. 在需要使用的父组件中通过import引入
  3. vuecomponents中注册
  4. 在模板中使用
//子组件 bar.vue
<template>
  <div class="search-box">
    <div @click="say" :title="title" class="icon-dismiss"></div>
  </div>
</template>
<script>
export default{
props:{
    title:{
       type:String,
       default:''
      }
    }
},
methods:{
    say(){
       console.log('明天不上班');
       this.$emit('helloWorld')
    }
}
</script>

// 父组件 foo.vue
<template>
  <div class="container">
    <bar :title="title" @helloWorld="helloWorld"></bar>
  </div>
</template>

<script>
import Bar from './bar.vue'
export default{
data(){
    return{
        title:"我是标题"
    }
},
methods:{
    helloWorld(){
        console.log('我接收到子组件传递的事件了')
    }
},
components:{
    Bar
}
</script>

小程序中,需要:

  1. 编写子组件
  2. 在子组件的json文件中,将该文件声明为组件

    {
      "component": true
    }
  3. 在需要引入的父组件的json文件中,在usingComponents填写引入组件的组件名以及路径

    "usingComponents": {
        "tab-bar": "../../components/tabBar/tabBar"
      }
  4. 在父组件中,直接引入即可

    <tab-bar currentpage="index"></tab-bar>

    具体代码:

    // 子组件
    <!--components/tabBar/tabBar.wxml-->
    <view class='tabbar-wrapper'>
      <view class='left-bar {{currentpage==="index"?"active":""}}' bindtap='jumpToIndex'>
        <text class='iconfont icon-shouye'></text>
        <view>首页</view>
      </view>
      <view class='right-bar {{currentpage==="setting"?"active":""}}' bindtap='jumpToSetting'>
        <text class='iconfont icon-shezhi'></text>
        <view>设置</view>
      </view>
    </view>
    

2.父子组件间通信

vue

父组件向子组件传递数据,只需要在父组件通过v-bind传入一个值,在子组件中,通过props接收,即可完成数据的传递,示例:

// 父组件 foo.vue
<template>
  <div class="container">
    <bar :title="title"></bar>
  </div>
</template>
<script>
import Bar from './bar.vue'
export default{
data(){
    return{        
        title:"我是标题"
    }
},
components:{
    Bar
}
</script>

// 子组件bar.vue
<template>
  <div class="search-box">
    <div :title="title" ></div>
  </div>
</template>
<script>
export default{
props:{
    title:{
       type:String,
       default:''
      }
    }
}
</script>

子组件和父组件通信可以通过this.$emit将方法和数据传递给父组件。

小程序

父组件向子组件通信和vue类似,但是小程序没有通过v-bind,而是直接将值赋值给一个变量,如下:

<tab-bar currentpage="index"></tab-bar>

此处, “index”就是要向子组件传递的值

在子组件properties中,接收传递的值

properties: {
    // 弹窗标题
    currentpage: {            // 属性名
      type: String,     // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
      value: 'index'     // 属性初始值(可选),如果未指定则会根据类型选择一个
    }
  }

子组件向父组件通信和vue也很类似,代码如下:

//子组件中
methods: {   
    // 传递给父组件
    cancelBut: function (e) {
      var that = this;
      var myEventDetail = { pickerShow: false, type: 'cancel' } // detail对象,提供给事件监听函数
      this.triggerEvent('myevent', myEventDetail) //myevent自定义名称事件,父组件中使用
    },
}

//父组件中
<bar bind:myevent="toggleToast"></bar>

// 获取子组件信息
toggleToast(e){
    console.log(e.detail)
}

如果父组件想要调用子组件的方法

vue会给子组件添加一个ref属性,通过this.$refs.ref的值便可以获取到该子组件,然后便可以调用子组件中的任意方法,例如:

//子组件
<bar ref="bar"></bar>

//父组件
this.$ref.bar.子组件的方法

小程序是给子组件添加id或者class,然后通过this.selectComponent找到子组件,然后再调用子组件的方法,示例:

//子组件
<bar id="bar"></bar>

// 父组件
this.selectComponent('#id').syaHello()

小程序父组件改变子组件样式

1.父组件将style传入子组件
2.父组件传入变量控制子组件样式
3.在父组件样式中,在子组件类名前面加上父组件类名

<view class='share-button-container' bindtap='handleShareBtn'>
   <share-button  product="{{goodProduct}}" type="1" back-color="#fff" fore-color="#9e292f" bind:error="on_error" />
</view>

.share-button-container .button--btn-navigator__hover{
  background: #fff;
}

小程序和vue在这点上太相似了,有木有。。。
clipboard.png

九、废话

还有好多地方没写,之后再慢慢加上、精简。感觉自己写的有点冗余,大佬勿喷!!!
如果觉得有帮助,希望帮忙点个赞和收藏

clipboard.png

查看原文

赞 224 收藏 147 评论 25

怀瑾以南辞 回答了问题 · 2020-10-12

解决对于Vue3.0版本项目,按需引入ant design vue失败

通过配置babel文件引入样式失败,目前只能直接通过在main.js文件中全局引入样式文件才能起作用.
对于less解析,需要另外安装相关插件转换.

关注 2 回答 2

怀瑾以南辞 提出了问题 · 2020-10-12

解决对于Vue3.0版本项目,按需引入ant design vue失败

我在vue3.0项目中进行尝试使用ant design vue框架,但是我在进行设置按需引入时出现了错误.
根据官网,已经安装有ant-design-vue和babel-plugin-import,根据配置要求
image.png
在vue3.0中,插件配置在babel.config.js文件中进行配置.
然后我在项目文件中进行按需引入尝试时报错.image.png
项目运行出现错误提示
image.png
,我先后根据网上其他人员的配置过程进行尝试,然而结果还是爆出相同的错误提示.

关注 2 回答 2

怀瑾以南辞 关注了用户 · 2020-08-27

xiangzhihong @xiangzhihong

著有《React Native移动开发实战》1,2、《Kotlin入门与实战》《Weex跨平台开发实战》、《Flutter跨平台开发与实战》和《Android应用开发实战》

关注 8582

怀瑾以南辞 关注了专栏 · 2020-08-27

SegmentFault 行业快讯

第一时间为开发者提供行业相关的实时热点资讯

关注 58803

认证与成就

  • 获得 0 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2020-08-27
个人主页被 319 人浏览