`

构建自己的DSL之二 抓取文本处理

阅读更多
转载请标明出处:http://fuliang.iteye.com/blog/1122051

公司的蜘蛛抓取的内容一个记录是以TAB分割的各个字段的值,并且随着各种分类得分、正文静态得分策略的添加,版本不断的演变。每次做抽样、分析、分类语料等文本处理都需要写一些样板式的代码,并且得到wiki查找指定版本每个字段的位置。构建一个好的DSL来自动处理这件事情能够省去很多重复的操作,只需要关注要处理的事情即可。
我们想提供简单自然的API来做事情,我们常用的需求有:
1、每次版本变更几乎不需要修改代码、只需要添加配置文件,比如新版本增加一个
travel_confidence,我们不需要修改代码就可以使用:
crawler_file.find_by_travel_confidence(90)
crawler_file.find_by_travel_confidence_gt(50)
...

2、可以自动的识别版本、并得到版本号:
crawler_file.version

3、按照次序给出各个字段的名字:
crawler_file.field_names

4、支持模糊查询字段的名字:
crawler_file.grep_fields(/url/)

5、根据某个字段的模糊或者精确的值来在一个文件中查找记录
#根据host来查找记录
crawler_file.find_by_host("www.9tour.cn") do |record|
    printf("%s\t%s\n", record.title, record.host)
end
#根据标题的字段来模糊查找
crawler_file.find_by_title_like(/线路/) do |record|
    puts record.title
end

6、数字的字段我们需要支持根据大小关系来查找记录:比如gt(>)、ge(>=)
eq(=)、le(<=)、lt(<)
#content_confidence大于50的记录
crawler_file.find_by_content_confidence_gt(50) do |record|
    printf("%s\t%s\n", record.title, record.content_confidence)
end

7、比较复杂的需求,我们可以写一些字段小过滤器,来找到需要的记录:
filter = lambda{|host,title| host == "www.9tour.cn" && title =~ /线路/}

crawler_file.find_by_fields([:host,:title],filter) do |record|
    printf("%s\t%s\n", record.host,record.title)
end

8.我们需要代码DRY。

我们下面看看如何完成这个功能,首先我们可以使用yaml来配置版本以及记录对应的字段:
v1:
    download_time: 0
    host: 1
    url: 2
    url_md5: 3
    parent_url_md5: 4
    crawl_level: 5
    loading_time: 6
    anchor_text: 7
    title: -4
    keywords: -3
    description: -2
    content: -1
v2:
    download_time: 0
    host: 1
    url: 2
    url_md5: 3
    parent_url_md5: 4
    crawl_level: 5
    loading_time: 6
    http_code: 7
    content_confidence: 8
    anchor_text: 9
    title: -4
    keywords: -3
    description: -2
    content: -1
...#中间省略

v9:
    download_time: 0
    host: 1
    url: 2
    url_md5: 3
    parent_url_md5: 4
    crawl_level: 5
    publish_time: 6
    http_code: 7
    content_confidence: 8
    list_confidence: 9
    feeling_confidence: 10
    travel_confidence: 11
    qnc_cat: 12
    qnc_chi: 13
    qnc_zhu: 14
    qnc_xing: 15
    qnc_you: 16
    qnc_gou: 17
    qnc_le: 18
    anchor_text: 19
    raw_title: -10
    title: -9
    keywords: -8
    description: -7
    content: -6
    lda_tag: -5
    location_text: -4
    location_confidence: -3
    hotel_confidence: -2
    gonglue_confidence: -1

以及是各个版本是数字字段的版本集合:
num_fields:
- download_time
- crawl_level
- publish_time
- content_confidence
- list_confidence
- feeling_confidence
- travel_confidence
- hotel_confidence
- gonglue_confidence

功能一:根据字段数来简单识别版本:
class VersionDetector
	@@field_num_version_map = {
		12 => 1,
		14 => 2,
		15 => 3,
		24 => 4,
		25 => 5,
		16 => 6,
		26 => 7,
		27 => 8,
		30 => 9
	};

	class << self
		def detect(file)
			version = -1
			if file.is_a?(String) then
				line = File.open(file) do |file| file.gets end
				version = @@field_num_version_map[line.split(/\t/).size]
			elsif file.is_a?(File) then
				before_pos = file.pos
				file.seek(0)
				line = file.gets
				version = @@field_num_version_map[line.split(/\t/).size]
				file.seek(before_pos)
			else
				raise ArgumentError.new 'Argument type: #{file.class} is error, must be a String or File type'
			end
			
			raise Exception.new 'Unkown version file format' if version.nil?

			return version
		end
	end
end

我们通过yaml来load版本配置:
require 'yaml'

class FieldConfig
    attr_reader :fields_map, :num_fields

    def initialize(version)
        config = YAML.load_file 'conf.yml'
        @fields_map = config["v#{version}"]
        @num_fields = config["num_fields"]
    end
end

我们根据配置文件动态的定义记录的字段,这样我们修改字段,不需要修改代码:
class CrawlerRecord
    def self.config(field_config)
        @@field_config = field_config
        attr_reader *(field_config.fields_map.keys) #动态定义字段的读方法
    end

    def initialize(raw_line)
        @raw_line = raw_line
        fields = raw_line.split(/\t/)
        @@field_config.fields_map.each do |key,value|#动态设置各个字段的值
            instance_variable_set("@" + key.to_s,fields[value])
        end
    end

    def raw
        @raw_line
    end
end

我们写一个CrawlerFile类来支持上面描述的一些功能:
class CrawlerFile

end

在这个类中定义数字字段支持的关系操作符:
@@num_fields_op = {
		:gt => ">",
		:lt => "<",
		:eq => "=",
		:ge => ">=",
		:le => "<="
};

字段和版本的读取方法:
attr_reader :field_names, :version

定义初始化方法:
def initialize(path)
	@file = File.new(path) #对应的文件
	@version = VersionDetector.detect(@file) #得到版本信息
	@@field_config = FieldConfig.new(@version) #得到该版本的配置
	@field_names = @@field_config.fields_map.keys #根据配置文件得到字段名字
	CrawlerRecord.config(@@field_config) #配置CrawlerRecord动态生成字段读方法
	define_help_method #定义帮助方法,来完成上面列举的其他功能
end

实现define_help_method
def define_help_method
		CrawlerFile.class_eval do 
#根据配置文件动态定义按照一个字段模糊查找方法find_by_xxx_like
			@@field_config.fields_map.keys.each do |field|
				define_method :"find_by_#{field}_like" do |regex,&block|
					if block.nil? then
						lines = []
						@file.each_line do |raw_line|
							line = CrawlerRecord.new(raw_line)
							lines << line if line.send(field) =~ regex
						end
						lines
					else
						@file.each_line do |raw_line|
							line = CrawlerRecord.new(raw_line)
							block.call(line)  if line.send(field) =~ regex
						end	
					end	
					@file.seek(0)
				end
#根据配置文件动态定义按照一个字段模糊查找方法find_by_xxx			
				define_method :"find_by_#{field}" do |value,&block|
					if block.nil? then
						lines = []
						@file.each_line do |raw_line|
							line = CrawlerRecord.new(raw_line)
							lines << line if line.send(field) == value
						end
						lines
					else
						@file.each_line do |raw_line|
							line = CrawlerRecord.new(raw_line)
							block.call(line) if line.send(field) == value
						end
					end
					@file.seek(0)
				end
			end
#为所有的数字字段动态定义按照大小关系查找的方法:			
			@@field_config.num_fields.each do |field|
				next if not @@field_config.fields_map[field]

				@@num_fields_op.keys.each do |op|
					define_method :"find_by_#{field}_#{op.to_s}" do |value,&block|
						op_val = @@num_fields_op[op]
						if block.nil? then
							lines = []
							@file.each_line do |raw_line|
								line = CrawlerRecord.new(raw_line)
								field_val = line.send(field)
								lines << line if eval("#{field_val} #{op_val} #{value}")
							end
							lines  
						else
							@file.each_line do |raw_line|
								line = CrawlerRecord.new(raw_line)
								field_val = line.send(field)
								block.call(line) if eval("#{field_val.to_i} #{op_val} #{value}")
							end
						end	
						@file.seek(0)	
					end
				end
			end
		end
	end


支持字段的组合的查询:
def find_by_fields(fields,cond_checker)
		if block_given? then
			@file.each_line do |raw_line|
				line = CrawlerRecord.new(raw_line)
				yield line if cond_checker.call(*fields.collect{|field| line.send(field) })
			end
		else
			lines = []
			@file.each_line do |line|
				line = CrawlerRecord.new(raw_line)
				lines << line if cond_checker.call(*fields.collect{|field| line.send(field)})
			end
			lines
		end
		@file.seek(0)
	end

关闭文件:
def close
   @file.close
end
2
1
分享到:
评论

相关推荐

    TXT文本替换和转换DSL文件

    TXT文本替换和转换成DSL文件,,DSL可直接用Abbyy_DLScompiler编译

    802DSL ALARM 8080 处理

    802D sl 8080报警处理,当西门子802Dsl出现8080报警可以按照文档进行处理

    elasticSearch查询语句DSL

    在这篇资源中,我们将详细介绍如何使用DSL来构建复杂的查询语句,以满足各种搜索需求。首先,我们将学习DSL的基本结构和语法规则,包括查询、过滤器、聚合和排序等核心概念。通过深入了解DSL的语法,您将能够灵活地...

    840Dsl建立第二编码器[汇编].pdf

    840Dsl建立第二编码器[汇编].pdf

    Groovy DSL

    使用Groovy可以快速灵活完成文本处理,数据库访问,XML处理等常见任务。研究表明,使用Groovy比使用Java写程序,代码量少3-10倍。 DSL是新一代基于领域设计专用语言。由于Groovy的超级灵活性,你可以使用Groovy快速...

    840D二次开发

    描述了基于840Dsl的二次开发技术,并且使用c++对数控系统进行开发。

    job-dsl-gradle-example, 使用Gradle构建和测试的作业DSL项目示例.zip

    job-dsl-gradle-example, 使用Gradle构建和测试的作业DSL项目示例 工作 DSL Gradle示例使用Gradle构建和测试的作业 DSL 项目示例。 签出这里演示文稿这里示例的演练。文件结构.├── src│ ├── jobs # DSL ...

    linear-dsl:用于构建自定义DSL

    用于构建自定义DSL。 设置 $ npm install --save linear-dsl 用法 解析自定义DSL查询。 const dsl = require ( 'linear-dsl' ) ; let data = dsl . parse ( 'a(foo) AND b(2 bars) OR c(4)' ) ; 验证查询映射。 ...

    LSM6DSL应用手册

    本文档详细的讲解了LSM6DSL的详细配置,其中讲解了数据的读取,唤醒模式,计步功能等。

    840D用户报警文本的制作

    840D用户报警文本的制作

    anko-example, 使用 Anko DSL构建的小型应用程序.zip

    anko-example, 使用 Anko DSL构建的小型应用程序 Anko示例项目这个 repo 中的代码展示了如何在 Android Gradle项目中设置 Anko 库。 应用程序非常简单,但是它显示了一些重要的Anko 。 在 Android Studio 中使用这个...

    dsl-1.2.7-API文档-中文版.zip

    赠送jar包:dsl-1.2.7.jar; 赠送原API文档:dsl-1.2.7-javadoc.jar; 赠送源代码:dsl-1.2.7-sources.jar; 赠送Maven依赖信息文件:dsl-1.2.7.pom; 包含翻译后的API文档:dsl-1.2.7-javadoc-API文档-中文(简体)版...

    GradleInAction--GroovyDSL后传天外飞仙篇

    本篇是哥自己根据《Gradle In Action》翻译的,继哥之前的Groovy DSL...通过gradle这个在项目构建领域的特定DSL,理解如何使用gradle来构建项目,以及更深入的理解前传中讲解的DSL体系。希望本书能给小伙伴们带来帮助。

    Ruby-Jbuilder通过一个构建式DSL创建JSON结构

    Jbuilder - 通过一个构建式DSL创建JSON结构

    buzzsaw:用于nokogiri的Web抓取DSL

    缠绕在Nokogiri DSL,由Nokogiri用于网络抓取。 安装 将此行添加到您的应用程序的Gemfile中: gem 'buzzsaw' 然后执行: $ bundle 或将其自己安装为: $ gem install buzzsaw 用法 Stretch.io用于DSL的就是这...

    DSL工程示范站,DSL规范

    DSL工程示范站,DSL图片规划,标准化施工

    LSM6DSL I2C总线驱动

    ST原厂LSM6DSL I2C总线驱动 In the 'driver' folder there are the driver files of Mems Sensor (.h and .c) to be included in your project. Driver documentation can be generate using Doxigen tool. The ...

    数控系统840d报警文本教程

    西门子系统840d报警文本的制作!doc的文本,希望对你有用!

    DSL测试方案.

    针对宽带DSL接入方式下的运营维护的难题,结合开发工作,介绍在DsL接入方式下自动测试的解决方案,对其中的技术要点做重点讨论。

    Android代码-Android RecyclerView的DSL库。

    Build RecyclerView With DSL &gt; Let's talk about how to use DSL in RecyclerView, this framework has been widely used in my project. It could help you build RecyclerView List in DSL Style like Anko ...

Global site tag (gtag.js) - Google Analytics