一个简单的爬虫习作,事件,cheerio,mongoose,coffeescript
发布于 10 年前 作者 zysam 4715 次浏览 最后一次编辑是 8 年前 来自 分享

一个简单的爬虫习作,以事件来组织代码。

主要受该文章影响。点我 scraper 的 github 一个爬虫无非是以下三个过程 : 载入网页 -> 解析内容 -> 保存数据

再加两个外部事件,错误处理及完成通知。

以事件来说明,是酱子的:

		@loadWebPage @url

		@on 'loaded',@parsePage

		@on 'parsed',@db

		@on 'error',@handleErr

		@on 'complete',@complete

1.载入网页,直取 node 原生 http.get ; 2.解析内容,这个不能写正则造轮子吧(其实我也不会写)!借用 cheerio 这个变态杀手好了 ; 3.数据保存,写本地就用 fs ,数据库就依赖 mongoose 。

所以 ,主框架 scraper.coffee 如下: 构造器 + 原型函数

EventEmitter = require('events').EventEmitter
cheerio = require 'cheerio'
http = require 'http'

STATUS_CODES = http.STATUS_CODES
show = console.log

class Scraper extends EventEmitter
	constructor : (@url) ->

	###
	init : ->
		@loadWebPage @url

		@on 'error',@handleErr

		@on 'loaded',@parsePage

		@on 'parsed',@db

		@on 'complete',@complete
	###

	loadWebPage : (opts,fn) ->
		fn = fn or ->
		if typeof opts is 'string'
			show 'Loading ' + opts
		else
			show 'Loading ' + opts.host + opts.path

		req = http.get opts,(res) =>
			body = ''
			if res.statusCode isnt 200
				@emit 'error',STATUS_CODES[res.statusCode]

			res.on 'data',(chunk) ->
				body += chunk

			res.on 'end', =>
				@emit 'loaded',body
				fn()
			return
		req.on 'error',(err) =>
			@emit 'error',err
		return

	parseLoad : (html) ->
		cheerio.load html

	parsePage : (html) ->
		show 'parse...'
		$ = @parseLoad html
		docs = []

		$('#shop-all-list ul li')
			.each (i,elem) ->
				model = new Object 
					shopName : ''
					link : ''
					pic : ''
					addr : ''
					cate : 
						life : new Array
						buss : new Array
					comment : new Array
					
				#console.log 'i:%s',i
				#console.log i + ':' + $('.txt .tag-addr span',@).text()
				model.shopName = $('.txt .tit a',@).attr('title')
				model.link = $('.txt .tit a',@).attr('href')
				model.pic = $('.pic a img',@).attr('data-src')
				
				$('.txt .tag-addr',@)
					.each (i,elem) ->
						model.addr = $('.addr',@).text()
						model.cate.life.push $('a span',@).eq(0).text(),$('a',@).eq(0).attr('href')
						model.cate.buss.push $('a span',@).eq(1).text(),$('a',@).eq(1).attr('href')

				model.comment.push $('.txt .comment span',@).attr('title')

				$('.txt .comment a',@)
					.each (i,elem) ->
						model.comment.push $(@).children().text()
				docs.push model

		@emit 'parsed',docs

	handleErr : (err) ->
		show 'has some error ,%s',err

	complete : ->
		show 'all have done!!'

module.exports = Scraper

个人模块 bot_test.coffee 继承主框架 , 以本地一个 html 来测试 , 拿大众点评来练手 。

fs = require 'fs'
Scraper = require './scraper'
show = console.log

class Tscraper extends Scraper
	constructor : (@path,@destpath) ->
		@init2Test()

	init2Test : ->
		show ' test...'

		@read2Test @path

		@on 'error',@handleErr

		@on 'loaded',@parsePage

		@on 'parsed',@write2Test

		@on 'complete',@complete

	read2Test : (path) ->
		fs.readFile path,{encoding:'utf8'},(err,data) =>
			show 'reading file.'
			if err then @emit 'error',err else @emit 'loaded',data

	write2Test : (data) ->
		data = JSON.stringify data
		fs.writeFile @destpath,data,(err) =>
			if err then @emit 'error',err else @emit 'complete'

	#your cheerio rule and change @on 'parsed'
	parseYour : (html) ->
		$ = @parseLoad html
		docs = []

		#your rule
		#
		#
		@emit 'parsed',docs

filepath = './test/gz_movie_p1.html'
destpath = './test/test_gz_movie.json'

scraper = new Tscraper filepath,destpath

实际上 , 身为一个合格的爬虫怎能没有并发呢! 正在 bot.coffee 的例子是酱子的:

mongoose = require 'mongoose'
Scraper = require './scraper'
Model = require './model'
show = console.log

url = 'http://www.dianping.com/search/category/2/10/g132'
COUNT = 0
PAGES_LIMITS = 50
DB_COUNT = 0

class YoScraper extends Scraper
	constructor : (@url) ->
		COUNT++
		@init()

	init : ->
		
		#回调这里好 , 载完网页立即载入 , loadWebPage 加个 callback
		@loadWebPage @url,wizard

		@on 'error',@handleErr

		@on 'loaded',@parsePage

		@on 'parsed',@db

		@on 'complete',@complete

	db : (docs) ->
		Model.create docs,(err) =>
			DB_COUNT++
			show '%s db runing.',DB_COUNT
			if err then @emit 'error',err else @emit 'complete'

	complete : ->
		show 'complete website : %s',DB_COUNT
		#全部完成后 , 断开 mongoose ,安静退出 , 深藏功与名 。
		if DB_COUNT is PAGES_LIMITS
			@exit()
	
	#有错误也不让它结束 , 爬下个。
	handleErr : (err) ->
		show 'has some error ,%s',err
		wizard()

	#your cheerio rule.
	parseYourWeb : (html) ->
		$ = @parseLoad html
		docs = []

		#your rule
		#
		#
		
		@emit 'parsed',docs
	exit : ->
		mongoose.disconnect()

geraterUrls = (limit) ->
	urls = []
	urls.push url
	urls.push url + 'p' + i for i in [2..limit]

	return urls

Urls = geraterUrls PAGES_LIMITS

wizard = ->
	if !Urls.length
		show 'Run all pages!!'
	else
		url = Urls.shift()
		scraper = new YoScraper url

numberOfParallelRequests = 20
wizard() for i in [1..numberOfParallelRequests]

如有载入网页->解析内容->载入网页 , 可以再写一个 ‘init’

另外: 前练习事件写法,无料近论坛有人争论 ‘事件的自言自语’ 是否合适 , 本人是小白 , 说得太高深 ,听不进 。 觉得事件写法,有如大白菜之美。

promise 的一点看法: promise 看来挺高大上 , 只要写出返回 promise 这种风格函数 ,再用 promise 来组织 。 我拿 mongoose 原生支持 promise 写了两句 。 普通函数有变成 promise 风格的工具 ,这就不好说了。

以上是我对爬虫 , 及异步的小小认识

1 回复

楼主写的很清楚 学习啦 多谢分享

回到顶部