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)