文盲的Python入门日记:第三十天,使用 xml 进行采集定义,进行定向采集,以 ccgp 为例

2023-05-16

本次采集实战,以 http://www.ccgp.gov.cn 为例,定向采集该站的政府采购信息。本文中,用到的采集类,请参考老顾的python入门23天和28天两篇文章。本文中所有出现的相关知识,在老顾的python入门系列文章中都有介绍,请缺课的同学自行补习。如果有其它语言采集池的同学想转到python做采集,可以私信老顾,一起讨论下哦。

---------------------------------------

之前,我们已经对 scrapy 做了否定,为什么呢,他不好用么?怎么说呢,他不能说不好用,但是不太符合老顾的习惯了,毕竟,老顾在没有使用 python 之前,就已经搞了7、8年的采集了,有很多采集,都已经形成自己的习惯了,比如,对ccgp这个网站的政采信息采集,老顾只定义了一个xml,然后放到老顾自己的采集工具里,他就可以正确的运行了,并不比scrapy什么的要差啊。老顾总不能为了转 python,就把以前所有定义好的信息全部放弃,就为了适应scrapy吧?多的不说,老顾手上的采集目标站点好几百个。。。难道你要我从新写好几百个蜘蛛?开玩笑吧。所以,老顾的目标是,将原有的站点信息导入到 python 中,一样可以使用~~~~加油!老顾!

说了那么多,先放一个站点的示例:以ccgp为例

<Sites>
    <Site type="2" filter="1" name="中国政府采购" url="http://www.ccgp.gov.cn/">
        <settings>
            <charset>utf-8</charset>
            <hasfilename>false</hasfilename>
        </settings>
        <regex>
            <reg>
                <![CDATA[http://[^"]+/cggg/(?![^"]+?\.html?)\w+/[^"]+(?<![/\\])(?=[/\\]?")]]>
            </reg>
            <settings>
                <charset>utf-8</charset>
                <hasfilename>false</hasfilename>
            </settings>
            <regex id="ccgplist">
                <match>
                    <reg><![CDATA[<li(?!\w)(?=([\s\S](?!<li(?!\w)))*?title=)(?=([\s\S](?!<li(?!\w)))*?\d+\.htm)([\s\S](?!<li(?!\w)))*</li(?!\w)[^<>]*?>]]></reg>
                    <url><![CDATA[http://[^"]+]]></url>
                    <item name="time"><![CDATA[(?<=发布时间:<span>)[^<>]*(?=</span>)]]></item>
                    <item name="area"><![CDATA[(?<=地域:<span>)[^<>]*(?=</span>)]]></item>
                    <settings>
                        <charset>utf-8</charset>
                        <hasfilename>true</hasfilename>
                    </settings>
                    <regex>
                        <item name="title"><![CDATA[(?<=<(h\d+)(?!\w)[^<>]*?>)([\s\S](?!<\1(?!\w)))*?(?=</\1(?!\w)[^<>]*?>)]]></item>
                        <item name="content"><![CDATA[(?<=(?:<(div) class="(vT_detail_content w760c|vF_detail_content)">|<div id="sign_content">))(([^<]|<(?!/?\1(?!\w))[^<>]*>)*|<\1(?!\w)[^<>]*>(?<DEPTH>)|</\1(?!\w)[^<>]*>(?<-DEPTH>))*(?(DEPTH)(?!))(?=</div>)]]></item>
                    </regex>
                </match>
            </regex>
            <regex>
                <page next="ccgplist">
                    <reg><![CDATA[(?<=<script language="javascript">Pager\(\{size:)\d+(?=, current:0, prefix:'index',suffix:'htm'\}\);</script>)]]></reg>
                    <loop>[url]index_[i].htm</loop>
                </page>
            </regex>
        </regex>
    </Site>
</Sites>

这是 ccgp 站点采集的定义了,其中,入口只有一个,根据入口,获得政采列表的链接,每个节点的大概意义参考下图

也就是我们的第一个 reg 节点中的信息,他用正则描述了这些公告的链接

http://[^"]+/cggg/(?![^"]+?\.html?)\w+/[^"]+(?<![/\\])(?=[/\\]?")

其中,每一个采集时,都有一个编码设置,一个包含文件名设置,用来方便计算我们的路径,前文也已经提到过了,他无法自动识别,所以我们在规则中给出他是不是需要在链接后追加/。

取得了列表页后,就可以得到列表中的每一项内容了,这就是第二个 reg 节点给出的信息了

<li(?!\w)(?=([\s\S](?!<li(?!\w)))*?title=)(?=([\s\S](?!<li(?!\w)))*?\d+\.htm)([\s\S](?!<li(?!\w)))*</li(?!\w)[^<>]*?>

同样是正则来提取页面中的内容

在 reg 节点后,跟随了一个 url 节点,这个节点是提取终端页链接的,用来采集终端页,获取更多信息,比如正文、采购方什么的

然后,最下边,我们有一个page节点,这个是用来定义翻页的,包括翻页链接的格式,是否有前缀、后缀什么的,毕竟每个站的翻页都有自己的规律,通过前缀后缀就可以把这些规律描述出来了。

嗯。。。通过这么一个站点的定义,大概就了解了,为什么老顾不愿意去转 scrapy 了,毕竟站点那么多,一个一个用 scrapy实现麻烦不说,还有很多更细致的需求也不能重用,代码重复率太高太高了,不符合开发人员的习惯。

好了,现在已经给出了这个原有的定义,现在老顾的工作就是写一个类,用来解析这个xml并开始采集,嗯,暂时先不多线程运行,先实现了采集再说,更多需求咱们边做边说

老顾先获取原来的配置信息中,ccgp 的节点 

from lxml import etree as ET
x = ET.parse(r'D:\\**********\\config.xml')
root = x.getroot()
sites = root.findall('.//Sites/Site')
print(ET.tostring(sites[0]).decode('utf8'))

第一个问题来了,汉字都变成 unicode 格式的了

print(ET.tostring(sites[0],encoding='utf8').decode('utf8'))

使用这个指令,子节点的中文到是都转出来了,当前节点的属性,还是unicode格式的

先不管这个了,我们先用变量 ccgp 引用这个站点设置

ccgp = sites[0]
print(ccgp.attrib)

结果,准备获取属性时,发现他又不乱码了。。。我也是服气了

不管他,先做个简单的测试

import re
from spider import Ajax
from lxml import etree as ET
x = ET.parse(r'D:\\work\\source\\CaiGou_Gather_Services\\CaiGou_Gather_Test\\config.xml')
root = x.getroot()
sites = root.findall('.//Sites/Site')
ccgp = sites[0]
ajax = Ajax()

def spider(target):
    tag = target.tag
    if tag.lower() == 'site':
        url = target.attrib['url']
        charset = target.findall('./settings/charset')[0].text
        isDirectory = target.findall('./settings/hasfilename')[0].text
        reg = target.findall('./regex/reg')[0].text.strip()
        if isDirectory == 'false':
            url = re.sub('/$','',url) + '/'
        ajax.charset = charset
        html = ajax.Http(url)
        urls = re.findall(reg,html,re.I)
        print(urls)

spider(ccgp)

先看看入口解析,能不能得到下一步的链接,运行后,结果符合预期

那么,我们就可以正式制作这个类了

import re
from lxml import etree as ET
from spider import Ajax

class XmlSettings:
	def __init__(self,file):
		'''初始化'''
		self.version = '0.1'
		self.ajax = Ajax()
		self.xml = ET.parse(file)
		self.root = self.xml.getroot()
		self.sites = self.root.findall('.//Sites/Site')
		self.queue = []
		self.done = []

	def parse(self,element,html=None,url=None):
		tag = element.tag
		if element.findall('./settings/charset'):
			charset = element.findall('./settings/charset')[0].text
			isDirectory = element.findall('./settings/hasfilename')[0].text
		else:
			charset = element.getparent().findall('./settings/charset')[0].text
			isDirectory = element.getparent().findall('./settings/hasfilename')[0].text
		if tag.lower() == 'site':
			url = element.attrib['url']
			if isDirectory == 'false':
				url = re.sub('/$','',url) + '/'
			self.queue.append(url)
			html = self.ajax.Http(url)
			nodes = element.findall('./regex')
			for node in nodes:
				self.parse(node,html)
		if tag.lower() == 'regex':
			if element.findall('./reg'):
				reg = element.findall('./reg')[0].text.strip()
				urls = re.findall(reg,html,re.I)
				for url in urls:
					if isDirectory == 'false':
						url = re.sub('/$','',url) + '/'
					self.queue.append(url)
					html = self.ajax.Http(url)
					nodes = element.findall('./regex')
					for node in nodes:
						self.parse(node,html,url)
			if element.findall('./match'):
				nodes = element.findall('./match')
				for node in nodes:
					self.parse(node,html,url)
			if element.findall('./page'):
				reg = element.findall('./page/reg')[0].text.strip()
				next = element.findall('./page/loop')[0]
				prefix = ''
				start = 1
				maxPage = 0
				pages = re.findall(reg,html,re.I)
				if 'start' in next.attrib:
					start = int(next.attrib['start'])
				if 'prefix' in next.attrib:
					prefix = next.attrib['prefix']
				if pages:
					maxPage = int(pages[0])
				for i in range(start,maxPage+1):
					next_url = next.text.strip()
					next_url = next_url.replace('[i]',str(i))
					next_url = next_url.replace('[fullurl]',url)
					next_url = next_url.replace('[url]',url)
					next_url = next_url.replace('[querystring]',url)
					self.queue.append(next_url)
					html = self.ajax.Http(next_url)
					node = element.getparent().findall('./regex/match')[0]
					#self.parse(node,html,next_url)
		if tag.lower() == 'match':
			regex = element.findall('./reg')[0].text.strip()
			prefix = element.findall('./url')[0].attrib['prefix'] if 'prefix' in element.findall('./url')[0].attrib else ''
			postfix = element.findall('./url')[0].attrib['postfix'] if 'postfix' in element.findall('./url')[0].attrib else ''
			matches = re.finditer(regex,html,re.I)
			for m in matches:
				match = m.string[m.span()[0]:m.span()[1]]
				print(match)

先简单的实现一些定义,其中parse方法算是递归方法了,当然这只是暂时的,因为真正要实现采集,还要考虑并发,考虑对方服务器封IP策略,考虑自己计算机运行速度等,这些咱们暂且先都不考虑,先看看能不能实现翻页,能不能得到终端页

在这段代码中,当碰到 regex/page 节点时,计算翻页,什么前缀,什么querystring全都考虑进来了,毕竟有的翻页是 /index_2.shtml,有的则是 /list.php?pg=2 这样的格式,所以,我这里定义的变量也相对多了一点,next_url就是计算完页码后的链接地址,为了测试,老顾把所有采集的页面链接,都放到了 queue 列表中了,我们可以在外边打印下 queue

from spider import XmlSettings
yy = XmlSettings(r'D:\\work\\source\\CaiGou_Gather_Services\\CaiGou_Gather_Test\\config.xml')
yy.parse(yy.sites[0])

for i in yy.queue:
    print(i)

可以看到,翻页后的链接的确已经生成成功了,然后,在翻页链接被采集后,有一句获得该链接对应的列表页解析节点的代码

					node = element.getparent().findall('./regex/match')[0]
					#self.parse(node,html,next_url)

根据这个节点,继续解析采集到的内容。。。不过为了减少运行时间,我暂时给他注释掉了,没有解析翻页后的列表页

而每个列表页的第一页,则进入到了

if tag.lower() == 'match':

这个代码片段,我在这个实现里,仅仅获取了列表页的 li 标签,并将其打印出来了

那么,到此为止,我们基本上可以对任意站点进行老顾这样的xml定义,进行不限制的采集了。

还是那句话,本文主要是针对已有的采集项,在转型到python时,用老顾定义的 Ajax类来实现转型的,而不是强制使用 scrapy 来从新定义的。

如果有同学有自己的采集池也要转型,可以私信老顾,咱们一起探讨一下怎么迁移哦。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

文盲的Python入门日记:第三十天,使用 xml 进行采集定义,进行定向采集,以 ccgp 为例 的相关文章

  • openwrt编译及第一个安装包教程

    Date 2017 03 14 Made SuperDeverloper Email na1206 64 live com Target For mt7688 based board 说明 xff1a 本人在学习过程中走了不少弯路 xff0
  • turtlebot3 Slam+nvigation仿真 ROS-lunar

    Date 2017 09 06 Author SuperDeveloper Description Slam simulation 说明 xff1a 1 Slam 初学笔记 xff0c 搭建slam仿真环境 xff1b 2 文章里的连接可能
  • 基于NVIDIA Xavier NX(ubuntu20.04)的Optitrack视觉定位 PX4+ros noetic(实物运行记录)

    提示 xff1a 文章写完后 xff0c 目录可以自动生成 xff0c 如何生成可参考右边的帮助文档 文章目录 前言一 xff1a 硬件准备两种界面化显示的方式无线连接有线连接 二 xff1a 软件准备1 远程登录软件 NoMachine2
  • ros机器人搭建总纲

    author xff1a superDeveloper date 2017 11 29 type note 近期准备搭建一个ROS机器人平台 xff0c 建立此博客记录搭建过程以及相关问题的解决办法 xff0c 作为笔记 xff0c 亦供相
  • ros gmapping 运行错误:Assertion 'beams<LASER_MAXBEAMS' failed>

    在使用真实激光器发布数据的时候 xff0c 出现了 Laser is mounted upward警告 xff0c 以及slam gmapping tmp buildd ros hydro openslam gmapping 0 1 0 2
  • realloc():invalid next size....错误

    Author SuperDeveloper Date 2018 1 2 在程序中使用了realloc函数 xff0c 更改结构体数组的大小 xff0c 错误代码如下 xff1a struct point span class hljs su
  • git简单命令笔记

    这是一篇关于git的使用笔记 xff0c 刚刚开始使用git 1 创建git本地仓库 xff1a 在你需要版本控制的项目Project根目录下右键点击Git Bash here执行git init 然后在该目录下生成 一个 git的隐藏文件
  • 源码编译Boost库的正确姿态

    源码编译Boost库的正确姿态 写在前面step 1 step 2 step 3 step4 写在前面 项目需要编译pcl库到arm平台 xff0c 交叉编译Boost xff0c Eigen3 Flann 之后再编译pcl库的时候总是报错
  • 第一讲、四旋翼的整体控制方案

    各位朋友 xff0c 我们工作室以后会长期更新一些飞行器干货 xff0c 本部分先介绍四旋翼的整体控制方案及相关设计 控制系统的框架如下 xff0c 借鉴网上来源图片 xff0c 传感器主要是姿态传感器 xff0c 对于大四轴而言 xff0
  • 网页中屏蔽鼠标右键、Ctrl+N、Shift+F10

    lt script language 61 34 Javascript 34 gt 屏蔽鼠标右键 Ctrl 43 N Shift 43 F10 F5刷新 退格键 屏蔽F1帮助 function window onhelp return fa
  • asp.net上一页下一页的部分代码

    lt asp linkbutton id 61 34 btnFirst 34 nclick 61 34 PagerButtonClick 34 runat 61 34 server 34 CommandArgument 61 34 0 34
  • Visual C# 编程操作Excel

    Visual C 编程操作Excel 2004 08 20 作者 xff1a 邵回祖 出处 xff1a ahcit http www yesky com SoftChannel 72342380468109312 20040819 1844
  • C#保存图片到IMAGE字段

    byte FileByteArray System IO MemoryStream ImageStream this sqlConnection1 ConnectionString 61 strConn try if this sqlCon
  • Android-蓝牙sco通话

    APP调用AudioManager startBluetoothSco frameworks base media java android media AudioManager java public void startBluetoot
  • Docker启动时的报错汇总

    八个Docker常见故障 https mp weixin qq com s 2GNKmRJtBGHhUyVBRbRgeA 八个Docker常见故障 报错一 xff1a error initializing graphdriver Docke
  • 利用JAVA操作EXCEL文件

    利用JAVA操作EXCEL文件 转载自 xff1a www csdn net 2003 年 1 月 在开源世界中 xff0c 有两套比较有影响的API可供使用 xff0c 一个是POI xff0c 一个是jExcelAPI 其中jExcel
  • 网站不能更新,错误 '80004005'

    网站数据转移到了另一台服务器 后台数据不能更新 因为程序原因也无错误信息报出 数据库为ACCESS数据库 初步估计是不是数据库只读或是NTFS的权限问题 对数据库目录添加EVERYONE和IIS的来宾帐户写入和修改权限 还是不能更新数据 最
  • vb6实现程序延时的几种方法

    VB6在开发发贴机时 提交数据要用到延时程序 让程延时和等待 从网上找了三种方法 三种方法以最后一种最好用 第一种如果不DOEVENTS的话 会使人感觉程序无反应一样 有以下方法 xff1a 1 使用Windows API函数Sleep 新
  • 初识SEO,SEO学习笔记一

    SEO 是一种方法 更是一种思想 如果只是为了关键字 那他就只是一种工具 一种方法 工具和方法总有过时的时候 而思想 则可以通达 可以明朗 可以提升层次 应当明的有关SEO的几个概念 目标关键词和长尾关词 一直以来 对这个概念都是只能意会
  • 【信号分析与处理】Matlab运算带有冲激函数的积分

    syms t span class token punctuation span span class token comment 定义变量 span span class token keyword int span span class

随机推荐