nodejs+request+cheerio实现网络爬虫

nodejs是运行在服务器上的JavaScript。网络爬虫的实现由很多种方式(采用的库有很多选择),网络爬虫的类型也有很多(定向爬虫,分布式爬虫)

流程介绍

访问网页:
网页的访问需要注意的是:某些网页 需要登录才能进入,所以需要使用cherom浏览器看下网页源码,登录操作是否采用表单form提交方式,这种访问通常是POST形式,不需要登录就能访问的网页通常采用GET访问方式。
访问一些需要提交form表单的网页要加上访问参数
为了使爬虫以最大限度访问网页,最好在访问参数里面设置访问头headers;访问请求网页可以采用request库
并下载网页:
访问网页成功后,会返回HTML流,该流其实就是字符串形式。解析网页也是解析该流
返回的HTML分为:response,body
response:应答流,里面包含响应头,body和一些其他内容。通常服务器应答的session,或者传过来的cookies也是放在响应头里面(如果有的话),如果只是想解析网页正文,只需要解析body即可,body是展示到浏览器给用户能看到的内容。
解析网页:
网页下载完成之后,需要把HTML流也就是HTML字符串转换成DOM对象,然后解析(这个过程和浏览器解析网页是一样的),在解析过程中我们可以查找DOM文档中是否有我们需要的信息,访问的方式和jQuery查找,定位网页元素一样(按照元素名,按照id,按照class,具体请参考jQuery);解析网页通常采用插件或者库进行,cheerio就是一个很好的解析库。内部实现采用jQuery

这里用nodejs+request+cheerio来演示
var request = require('request');
var cheerio = require('cheerio');
var username = "yangqiang"
var _password = "********"
var login_page = "http://192.168.2.*:8080/rdms/authorize.do?method=login"
var search_bug_page = "http://192.168.2.*:8080/rdms/common/search/searchAction!search.action"
var search_bug_result_page = "http://192.168.2.*:8080/rdms/qm/bug/bugAction.do?action=getBugDetail&id="
var _id = ''
//设置请求头,模拟浏览器
var headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36'
}

function start(_bug, callback) {
    //console.log(" start login... ")
    //设置访问参数,并封装成一个json对象,里面包含URL,form表单,访问方式,请求头
    //更多的参数设置参考http://www.jb51.net/article/58465.htm 。补充options  还可以设置代理(目前还不知道代理有什么用)proxy: Proxy.GetProxy(),
    var login_options = {
        url: login_page,
        form: {
            "username": username,
            "password": _password
        },
        method: 'POST',
        headers: headers,
        //sendImmediately: false  //默认为真,发送一个基本的认证header。设为false之后,收到401会重试(服务器的401响应必须包含WWW-Authenticate指定认证方法)。
    };
    //设置cookie 默认情况cookie禁止,这里使用全局cookie
    //request 原文参考https://github.com/request/request 中文参考http://www.open-open.com/lib/view/open1435301679966.html
    var _request = request.defaults({jar: true});
    _request(
        login_options
        , function (error, response, body) {
            if (error) {
                console.log("longin error,please check user &password");
                return console.error(error);
            }
            else {
                search_options = {
                    url: search_bug_page,
                    form: {
                        "queryString": _bug  //这里的queryString必须和要访问的网页form表单参数对应
                    },
                    method: 'POST',
                    headers: headers
                }
                _request(search_options,
                    function (error, response, body) {
                        if (error) {
                            console.log('get id query error');
                            return error;
                        }
                        //解析搜索bug返回的页面,这里只解析body
                        //cheerio 原文参考https://www.npmjs.com/package/cheerio 中文参考https://cnodejs.org/topic/5203a71844e76d216a727d2e
                        var $ = cheerio.load(body);
                        $('a').each(function () {
                            var $table_node = $(this);
                            var href = $table_node.attr('onclick');
                            var id = href.trim().substring(66, href.length - 13);
                            _id = id;
                        });
                        var url = search_bug_result_page + _id + "&popup=true".trim();
                        callback(url.toString())
                    });
            }
        });
}

function get_data(_bug, callback) {
    var login_options = {
        url: login_page,
        form: {
            "username": username,
            "password": _password
        },
        method: 'POST',
        headers: headers,
        //sendImmediately: false  //默认为真,发送一个基本的认证header。设为false之后,收到401会重试(服务器的401响应必须包含WWW-Authenticate指定认证方法)。
    };
    //设置cookie 默认情况cookie禁止
    var _request = request.defaults({jar: true});
    _request(
        login_options
        , function (error, response, body) {
            if (error) {
                console.log("longin error,please check user &password");
                return console.error(error);
            }
            else {
                search_options = {
                    url: search_bug_page,
                    form: {
                        "queryString": _bug
                    },
                    method: 'POST',
                    headers: headers
                }
                _request(search_options,
                    function (error, response, body) {
                        if (error) {
                            console.log('get id query error');
                            return error;
                        }
                        //解析搜索bug返回的页面
                        var $ = cheerio.load(body);
                        $('a').each(function () {
                            var $table_node = $(this);
                            var href = $table_node.attr('onclick');
                            var id = href.trim().substring(66, href.length - 13);
                            _id = id;
                        });
                        var url = search_bug_result_page + _id + "&popup=true".trim();
                        //callback(url.toString())
                         var search_bug_result_options = {
                         url: url,
                         method: 'GET',
                         headers: headers,
                         }
                             _request(
                             search_bug_result_options,
                             function (_error,_response,_body) {
                             if (_error) {
                             console.log("get_ bug content error");
                             return console.error(_error);
                             }
                                 var results_data={};
                                 var $ = cheerio.load(_body);
                                 var td = $('td');
                                 td.each(function (td) {
                                     var node_td = $(this);
                                     if (node_td.text()=='处理人')
                                     {
                                         var current_handler=node_td.next().find('a').text().trim();
                                         results_data.current_handler=current_handler;
                                         callback(results_data);
                                     }
                             });
                             });
                    });
            }
        });
}
exports.start = start;
exports.get_data = get_data;
Python代码示例
Python代码使用了很多库,需要安装
# coding:utf8
import ConfigParser
import cookielib
import json
import re
import string
import urllib
import urllib2
import time
import bs4
class HtmlDownloader(object):
    login_page = "http://192.168.2.92:8080/rdms/authorize.do?method=login"
    search_bug_page="http://192.168.2.92:8080/rdms/common/search/searchAction!search.action"
    search_bug_result_page="http://192.168.2.92:8080/rdms/qm/bug/bugAction.do?action=getBugDetail&id="
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36"}
    cookie = cookielib.CookieJar()
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
    project=''
    version=''
    title=''
    reporter=''
    handler=''
    causes=''
    exchange_record=''
    def download(self,url):
        if url is None:
            return None
        response=urllib2.urlopen(url)
        #if response.getcode()!=200:
            #return None
        return response.read()

    def login(self, username, password):
        print '[**mainsoft**]       Login'
        try:
            data = urllib.urlencode({
                "username": username,
                "password": password,
                "productCode": 'Analysis'})
            urllib2.install_opener(self.opener)
            req = urllib2.Request(self.login_page, data, self.headers)
            response = self.opener.open(req)
            page = response.read().decode("utf-8").encode("gbk")
            return page
        except Exception, e:
            print str(e)
    def search_bug(self,bug_num):
        print "输入的BUG号:"+bug_num
        try:
            bug_query=urllib.urlencode({
                "queryString":bug_num
            })
            urllib2.install_opener(self.opener)
            req = urllib2.Request(self.search_bug_page, bug_query, self.headers)
            response=self.opener.open(req)
            bug_result_page=response.read()
            print "搜索BUG号返回结果页面:"+bug_result_page
        except Exception, e:
            print str(e)
        print "开始解析BUG搜索结果HTML"
        soup = bs4.BeautifulSoup(bug_result_page, 'html.parser',from_encoding="gbk")
        link_a_node=soup.find('a',href='#')
        bug_id=link_a_node['onclick']
        print link_a_node.get_text()
        _id=bug_id[66:-13]
        print _id
        #search_result_node=[]
        search_result_node=soup.find_all('span',class_='highlight')
        #print search_result_node
        #print search_result_node[1]
        #print search_result_node[2:-1]
        url=self.search_bug_result_page+_id+"&popup=true"
        print url
        request = urllib2.Request(url,'',self.headers)
        response = self.opener.open(request)
        bug_detailed_html=response.read()
        print "根据id获取BUG详情页面....."
        #print bug_detailed_html
        print "开始解析BUG详情页面....."
        soup_detailed=bs4.BeautifulSoup(bug_detailed_html,'html.parser',from_encoding="gbk")
        bug_detailed_node=soup_detailed.find('table',align='center')
        print "BUG详情页面解析结果如下:"
        #print bug_detailed_node
        flag=0
        for  tr_line in bug_detailed_node:
            #print tr_line
            for td_line in tr_line:
                flag+=1
                #print flag
                #print td_line
                if flag==5:
                    self.project=td_line
                if flag==15:
                    self.title=td_line
                if flag==95:
                    self.reporter=td_line
                if flag==99:
                    self.handler=td_line
                if flag==152:
                    self.causes=td_line
        print "项目名称:\n"
        print self.project
        print "标题:\n"
        print self.title
        print "报告人:\n"
        print self.reporter
        print "处理人:\n"
        print self.handler
        print "原因分析:\n"
        print self.causes
        print "交流记录:\n"
        print self.exchange_record


if __name__ == '__main__':
    bug='B160826-334'
    username='yangqiang'
    password='07030501310'
    html_down=HtmlDownloader()
    html_down.login(username,password)
    html_down.search_bug(bug)

定向爬虫

主要是针对某个特定网站/服务器进行数据采集示例如上面

分布式爬虫

分布式爬虫主要是在网页里面有很多链接URL,需要分别对这些URL进行访问。由此形成一个爬虫树,分布式爬虫比定向爬虫要复杂。
分布式爬虫需要有
网页调度器
网页调度器主要负责协调其他管理器工作,处于整个爬虫中心位置

# coding:utf8
#爬虫总调度程序
from baike_spider import html_downloader
from baike_spider import html_outputer
from baike_spider import html_parser
from baike_spider import url_manager

class SpiderMain(object):
    def __init__(self):
        self.urls=url_manager.UrlManager()
        self.downloader=html_downloader.HtmlDownloader()
        self.parser=html_parser.HtmlParser()
        self.outputer=html_outputer.HtmlOutputer()

#调度器核心算法
    def craw(self,root_url):
        count=1
        self.urls.add_new_url(root_url)
        while self.urls.has_new_url():#继续循环的条件是URL管理器有新的URL
            try:#异常捕获
                new_url=self.urls.get_new_url()#从URL管理器获取
                print 'craw %d : %s'%(count,new_url)
                html_cont=self.downloader.download(new_url)#调用下载器去下载网页
                new_urls,new_data=self.parser.parse(new_url,html_cont)#调用解析器解析网页
                #上面的解析器返回两个数据,新的URL管理器,解析的结果
                self.urls.add_new_urls(new_urls)
                self.outputer.collect_data(new_data)
                if count==100:
                    break
                count+=1
            except:
                print 'craw failed'
        self.outputer.output_html()
#处理main函数
if __name__=="__main__":
    root_url="http://baike.baidu.com/view/21087.htm" #入口URL
    obj_spider=SpiderMain()
    obj_spider.craw(root_url)

URL管理器
主要负责管理所有已经爬取过的URL和等待爬取的URL,URL的收集,URL的流出

# coding:utf8
class UrlManager(object):
    def __init__(self):
        #在构造方法中初始化两个set集合
        self.new_urls=set()
        self.old_urls=set()
        #new_urls表示待爬的URL集合
        #old_urls表示已经爬过的URL集合
    def add_new_url(self, root_url):
        if root_url is None:
            return
        if root_url not in self.new_urls and root_url not in self.old_urls:
            #检查入口URL,如果不在新URL管理器中,也不在旧的URL管理器中,这说明该URL是新的
            self.new_urls.add(root_url)
            #add_new_urls将一个待爬URL集合添加进来,内部掉用add_new_url()
            #为什么先调用add_new_url()后面调用add_new_urls()
            #因为第一次执行程序时,只提供了一个URL入口,爬第一个一个网页,该网页里面有很多符合特定格式的URL,然后分别爬
            #整个执行的过程 类似一个URL树结构,越到后面 需要爬的URL越多
    def add_new_urls(self, new_urls):
        if new_urls is None or len(new_urls)==0:#如果待添加的URL集合为空,或者长度为0(注意这两者不一样)
            return
        for new_url in new_urls:
            self.add_new_url(new_url)

    def has_new_url(self):
        return len(self.new_urls)

    def get_new_url(self):
        new_url=self.new_urls.pop()#pop从集合中取出一个URL,并移除
        self.old_urls.add(new_url)#将取出的URL添加到旧URL集合中
        return new_url

网页解析器
主要对下载后的HTML流解析出数据

# coding:utf8
from bs4 import BeautifulSoup //注意这里的意思是说:从bs4模块中导入BeautifulSoup 这个类
import re
import urlparse
class HtmlParser(object):
    #网页解析器里面只有一个方法
    def parse(self, page_url, html_cont):
        if page_url is None or html_cont is None:
           return
        soup=BeautifulSoup(html_cont,'html.parser',from_encoding='utf-8')
        #从一个网页爬出的URL集合
        new_urls=self._get_new_urls(page_url,soup)
        #解析当前页面的元素
        new_data=self._get_new_data(page_url,soup)
        return new_urls,new_data
        #python可以返回两个值

    def _get_new_urls(self, page_url, soup):
        new_urls=set()
        # /view/456.htm 使用正则表达式进行模糊匹配 /view/xxx.htm xxx是数字格式
        #查找所有a节点,也就是获取网页中所有的链接节点
        links=soup.find_all('a',href=re.compile(r"/view/\d+\.htm"))
        for link in links:
            new_url=link['href']
            #使用urlparse模块提供的URL拼接函数
            new_full_url=urlparse.urljoin(page_url,new_url)
            new_urls.add(new_full_url)
        return new_urls
    def _get_new_data(self,page_url,soup):
        res_data={}  #创建一个字典 存放 返回的数据集合
        #URL 将URL添加到结果字典里,key是URL 作用是方便后续查看
        res_data['url']=page_url
        #标题处理
        #

Python

来源于百度百科Python词条源码 title_node=soup.find('dd',class_="lemmaWgt-lemmaTitle-title").find("h1") res_data['title']=title_node.get_text() #简介div处理 #
Python简介div块区 summary_node=soup.find('div',class_="lemma-summary") res_data['summary']=summary_node.get_text() return res_data

网页下载器
下载器主要是根据URL地址下载对应HTML流,下载器还要对目标网页的访问,提交from表单,接收数据
下面示例代码有部分是URL下载器的功能,另外部分是别的管理器的功能(没有来得及分离)

# coding:utf8
import ConfigParser
import cookielib
import json
import re
import string
import urllib
import urllib2
import time
import bs4
class HtmlDownloader(object):
    login_page = "http://192.168.2.92:8080/rdms/authorize.do?method=login"
    search_bug_page="http://192.168.2.92:8080/rdms/common/search/searchAction!search.action"
    search_bug_result_page="http://192.168.2.92:8080/rdms/qm/bug/bugAction.do?action=getBugDetail&id="
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36"}
    cookie = cookielib.CookieJar()
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
    project=''
    version=''
    title=''
    reporter=''
    handler=''
    causes=''
    exchange_record=''
    def download(self,url):
        if url is None:
            return None
        response=urllib2.urlopen(url)
        #if response.getcode()!=200:
            #return None
        return response.read()

    def login(self, username, password):
        print '[**mainsoft**]       Login'
        try:
            data = urllib.urlencode({
                "username": username,
                "password": password,
                "productCode": 'Analysis'})
            urllib2.install_opener(self.opener)
            req = urllib2.Request(self.login_page, data, self.headers)
            response = self.opener.open(req)
            page = response.read().decode("utf-8").encode("gbk")
            return page
        except Exception, e:
            print str(e)
    def search_bug(self,bug_num):
        print "输入的BUG号:"+bug_num
        try:
            bug_query=urllib.urlencode({
                "queryString":bug_num
            })
            urllib2.install_opener(self.opener)
            req = urllib2.Request(self.search_bug_page, bug_query, self.headers)
            response=self.opener.open(req)
            bug_result_page=response.read()
            print "搜索BUG号返回结果页面:"+bug_result_page
        except Exception, e:
            print str(e)
        print "开始解析BUG搜索结果HTML"
        soup = bs4.BeautifulSoup(bug_result_page, 'html.parser',from_encoding="gbk")
        link_a_node=soup.find('a',href='#')
        bug_id=link_a_node['onclick']
        print link_a_node.get_text()
        _id=bug_id[66:-13]
        print _id
        #search_result_node=[]
        search_result_node=soup.find_all('span',class_='highlight')
        #print search_result_node
        #print search_result_node[1]
        #print search_result_node[2:-1]
        url=self.search_bug_result_page+_id+"&popup=true"
        print url
        request = urllib2.Request(url,'',self.headers)
        response = self.opener.open(request)
        bug_detailed_html=response.read()
        print "根据id获取BUG详情页面....."
        #print bug_detailed_html
        print "开始解析BUG详情页面....."
        soup_detailed=bs4.BeautifulSoup(bug_detailed_html,'html.parser',from_encoding="gbk")
        bug_detailed_node=soup_detailed.find('table',align='center')
        print "BUG详情页面解析结果如下:"
        #print bug_detailed_node
        flag=0
        for  tr_line in bug_detailed_node:
            #print tr_line
            for td_line in tr_line:
                flag+=1
                #print flag
                #print td_line
                if flag==5:
                    self.project=td_line
                if flag==15:
                    self.title=td_line
                if flag==95:
                    self.reporter=td_line
                if flag==99:
                    self.handler=td_line
                if flag==152:
                    self.causes=td_line
        print "项目名称:\n"
        print self.project
        print "标题:\n"
        print self.title
        print "报告人:\n"
        print self.reporter
        print "处理人:\n"
        print self.handler
        print "原因分析:\n"
        print self.causes
        print "交流记录:\n"
        print self.exchange_record


if __name__ == '__main__':
    bug='B160826-334'
    username='yangqiang'
    password='07030501310'
    html_down=HtmlDownloader()
    html_down.login(username,password)
    html_down.search_bug(bug)

你可能感兴趣的:(JavaScript,nodejs)