Scrapy
Engine Spider
Scheduler Downloader Middleware
Spider Downloader
Downloader Item Pileline
new request 7)7)
Item
Item and new request Response
6)Working with
Responses
5)Responses
Completed
Download 4)
5)
Response Request
Request
Request
Start Download
3)Next URL
3)4)
4)
2)2)
Start URL
1)Open a domain
7)
6)6)
由于公司PLM 系统不支持导出数据及定期提醒功能,查询物料信息追溯到每个任务项复制到文本耗时5个工作日/月,针对这个痛点决定采用非侵入性的基于Python 爬虫技术来解决。这里爬虫顾名思义,就像蜘蛛一样爬在“蜘蛛网”爬遍网中的每个角落,把所有“食
物”捕获下来。将爬虫运用到工作中就是把网页中有意义的信息捕获下来保存、分析并可视化的过程。经过调研发现Scrapy 是基于Python 写的爬虫架构,轻量级,架构清晰,接口灵活,并集异步下载、队列、解析、管道等功能于一体,优势显著,运用Scrapy 成熟架构来实现PLM 系统定向爬虫。
1爬虫架构
根据图1介绍Scrapy 架构中各大组件:引擎Scrapy Engine、调度器Scheduler、下载器Downloader、爬虫Spider、项目管道Item Pipeline、下载中间件Downloader
Middlewares 及爬虫中间件Spider Middlewares。引擎负责控制数据流在所有组件中流转并触发事件,调度器将引擎发送的Request 入队便于请求时提供,下载器获取网页信息,爬虫是用户编写来分析Response 和Item,项目管道负责Item 的存储,下载中间件是用户编写用于处理引擎和下载器之间的特定钩子,Spider 中间件是将Spider 的输入和输出与引擎钩连起来。
通过数据流的流向来认知Scrapy 工作流程及各组件间协调,如图2所示。(1)是指爬虫启动时引擎打开PLM 系统网站;
(2)Spider 接收到引擎请求起始网
址Start URL (Uniform Resource Locator)后,将初始化函数中的起始网址返回给引擎并打开起始网址;(3)
引擎将爬虫下一个网址的Request 传递给调度器,调度
器将其返回给引擎;
(4)引擎将下一个网址的请求发
作者简介:王晓蕊(1987-),女,软件测试工程师,硕士,研究方向:软件测试开发。
在PLM 系统中实现分布式爬虫
王晓蕊
(厦门ABB 智能科技有限公司,福建厦门361101)
要:近年来,PLM (Product Lifecycle Management )产品生命周期在公司中越来越被重视,它集成了产品相关的人力、流程、信息等整个生命周期,支持产品创建、管理、分发一系列解决方案。但引入的PLM 系统平台不支持查询导出及任务提醒功能,提取整合数据成为了工作痛点。Scrapy 是基于Python 开发的一个快速、高层次网站爬虫事件架构,可提取页面结构化源数据并将其清洗、格式化、存储到数据库等操作级联起来。采用Scrapyd 开源平台部署分布式爬虫实现多台服务器资源并发地爬取数据,通过Metabase 开源平台呈现出来。
关键词:Python 语言;Scrapy 架构;分布式;Metabase 平台
图1Scrapy 架构
图2爬虫数据流
Item Pipeline
Scheduler
Scrapy Engine
Requests Items
Spider
Middlewares
Downloader Middlewares
Requests
Intenet
Downloader
Responses
送给下载中间件让其钩连下载器执行网页下载直至完毕;
(5)下载器将网页下载后的Response 传递给下载
中间件再返回给引擎;
(6)引擎接收Response 并发送
给Spider 中间件去触发Spider 解析网页的Response,Spider 返回爬虫数据项和下一个网址的Request 给引擎;(7)引擎将接收的数据项发送给项目管道,返回的下一个网址Request 给调度器。下一个网页的爬虫从第二步开始重复执行直至调度器中没有新的Request,引擎关
闭PLM 系统网站,完成爬虫。
2
爬虫编程
2.1爬虫环境部署及项目构建
爬虫编程前需安装Scrapy 并配置电脑环境变量为
编程提供环境。首先,对Python 安装及环境变量配置,在下载最新版本后一键安装即可。其次,安装依赖包。根据项目需求对python 依赖包安装可以新建re⁃ 文件,将依赖包写入并保存,包含且不局限于:Scrapy/Scrapyd/re/Scrapydweb/Selenium/Pymysql/configpars er/pandas/xlwings/metabase。在系统终端输入:pip install - 会自动将文件中包进行下载安装。最后,构建爬虫项目。在工作目录C:\work 下运行终端输入命令Scrapy startproject plm_crawler,在work 目录下创建
plm_crawler 文件夹,内部结构如图3所示。
spiders 文件夹是用户定义爬虫类并编写爬虫逻辑实
现数据提取。新建plm_spider.py 须继承scrapy.spider 类,代码如下:
1.class PlmSpiderCrawl(scrapy.Spider):
2.name ='plm_crawler'
3.allowed_domains =['lp-global-plm.abb']
其中,name 是爬虫的名字,必须是唯一的,与set⁃tings 中的BOT_NAME 一致。allowed_domains 是指爬虫网站,lp-global-plm.abb 就是PLM 的网站,将不在
此允许范围内的域名过滤掉不执行爬取。2.2编程解决方案2.2.1爬虫起始URL
对PLM 系统网页爬虫,必须先规定起始网址,然
后确定提取网页哪些信息,最后对数据存储及呈现。因PLM 系统打开网站时会弹出原生框输入密码和用户名运用Selenium 库处理无效,但采用带参方式登录可避开原生框,故PLM 系统的起始网址为:start_urls=user:**************************/Windchill。在plm_spi der.py 添加init 函数,调用Chrome 来登录PLM 系统:1.from selenium import webdriver 2.def _init_(self,**kwargs)
3.option =webdriver.ChromeOptions()
4.option.add_argument('headless')
5.(url)2.2.2网页下载
网页下载是用户根据需求编写代码进行爬取数据。
在爬虫前先配置settings.py 文件,打开发现很多参数被注释掉,需根据爬虫需求对参数进行修改和激活。ROBOTSTXT_OBEY =True 是指爬虫过程中遵守
< 规则,作用是告诉搜索引擎,爬虫可以对哪些
目录下的网页信息进行提取。启动Scrapy 最先访问网站的机器规则,然后决定爬取范围。有时所需的内容恰好被r 禁止访问,故拒绝遵守机器人协议将此项配置修改为False。
DEFAULT_REQUEST_HEADERS 激活默认使用的请
求头并伪装,防止被封杀。伪装user-agent 随机请求头,在settings.py 中建请求头池,然后随机调用,代码实现如下。
1.import random
2.USER_AGENT_LIST=[
3."Mozilla/5.0(Windows NT 6.1;WOW64)AppleWe⁃bKit/537.1(KHTML,like Gecko)Chrome/22.0.1207.1Safari/537.1",
4."Mozilla/
5.0(X11;CrOS i6862268.111.0)AppleWe⁃bKit/53
6.11(KHTML,like Gecko)Chrome/20.0.1132.57Safari/536.11",
5."Mozilla/5.0(Windows NT
6.2)AppleWebKit/536.3(KHTML,like Gecko)Chrome/19.0.1062.0Safari/536.3",6."Mozilla/5.0(Windows NT 6.1;WOW64)AppleWe⁃bKit/536.3(KHTML,like Gecko)Chrome/19.0.1062.0Safari/536.3",
7."Mozilla/5.0(Windows NT 6.1;WOW64)AppleWe⁃bKit/536.3(KHTML,like Gecko)Chrome/19.0.1061.1Safari/536.3",
8."Mozilla/5.0(Windows NT 6.1)AppleWebKit/536.3(KHTML,like Gecko)Chrome/19.0.1061.1Safari/536.3",
图3Scrapy 内部结构
#部署架构文件#项目包名#定义数据字段#中间件
#处理数据方法#项目参数设置#
存放爬虫的地方
plm_crawler/
scrapy.cfg plm_crawler/__init__.py items.py
middlewares.py pipelines.py setting.py spiders/
__init__.py
9."Mo zilla/5.0(Windows NT6.2)AppleWebKit/536.3 (KHTML,like Gecko)Chrome/19.0.1061.0Safari/536.3", 10."Mozilla/5.0(X11;Linux x86_64)AppleWebKit/ 535.24(KHTML,like Gecko)Chrome/19.0.1055.1Sa⁃fari/535.24",
11."Mozilla/5.0(Windows NT6.2;WOW64)AppleWe⁃bKit/535.24(KHTML,like Gecko)Chrome/19.0.105
5.1 Safari/535.24"
12."Mozilla/5.0(Windows NT6.1;Win64;x64)AppleWe⁃bKit/537.36(KHTML,like Gecko)Chrome/83.0.4103.61 Safari/537.36"
13."Mozilla/5.0(Windows NT6.1;Win64;x64)AppleWe⁃bKit/537.36(KHTML,like Gecko)Chrome/70.0.3538.97 Safari/537.36"
14.]
15.DEFAULT_REQUEST_HEADERS={
16.'Accept':'text/html,application/xhtml+xml,applica⁃tion/xml;q=0.9,*/*;q=0.8',
17.'Accept-Language':'en',
18.'User-Agent':random.choice(USER_AGENT_LIST)
19.}
DOWNLOAD_DELAY=3激活下载延时,防止无间隔爬取被误认为是攻击而被封杀。
ITEM_PIPELINES激活管道添加存储处理:
1.ITEM_PIPELINES={
'plm_crawler.pipelines.PlmCrawlerPipeline':300} DOWNLOADER_MIDDLEWARES激活并添加中间件处理方法用来操控爬虫过程中Requests和Response 网页处理。
1.DOWNLOADER_MIDDLEWARES={
'plm_crawler.middlewares.PlmCrawlerSpiderMid⁃dleware':500}
配置文件修改后,开始执行爬虫。因PLM系统复杂性及数据量大,每次爬取数据耗时8天,爬虫过程中因网络异常会导致网页白屏、请求404问题导致多次请求失败报错终止。为了解决此问题,分解任务类型,分布式爬虫。
保证起始URL不变情况下,变更PLM系统请求条件。将任务按年份划分来预防条数过多而影响爬虫效率。添加条件判断用configparser库实现配置文件读取。如,2020年非标订单进行爬虫,依据配置文件中Con⁃dition=1参数,读取关键条件并在URL中依次模拟输入Problem Reports和SCRAPY_2020再回车,等待网页响应后呈现的就是本次爬虫任务单,代码实现如下。
1.class PlmSpiderMiddleware(object):
2.def process_request(self,request,spider):
3.if spider.name=="plm_spider":
4.l1total=a.get('L1Total','0'))#传参,判断爬虫的url总数
5.if l1total==0:#01.执行PLM爬虫,登陆PLM系统
7.changecondition=GetParasSectionKey("Condition"," condition"+str(conditionmode))#条件
8.change=GetParasSectionKey("Condition","change" +str(conditionmode))#读取change文件夹
9.#点击变更参数和条件
10.changetab=WebDriverWait(spider.brower,120). until(
11.EC.presence_of_element_located((By.XPATH,'//a [contains(text(),"'+change+'")]'))
12.)
14.EC.presence_of_element_located((By.XPATH,'//in⁃put[@id="folderbrowser_PDMfilterSelect"]'))
15.)
Spider在接收到引擎发送的Response后用选择器进行解析,通过特定的XPath来选择HTML中的某一部分。在Spider中用户使用parse解析方法编写代码获得Response对象,对其使用/a、/div、/td等html
标签结合XPath定位所需元素进行解析,尽可能避免元素绝对路径,尤其是当元素没有唯一id或name时,充分使用模糊定位对HTML文本中检索关键词而获得所需信息。1.response.xpath('//*[@id="infoPageIdentity"]/text()'). get()
4.
response.xpath("//a[re:test(@href,'^\/index\.php\?m=
News&a=details&id=1&NewsId=\d{1,4}')]")
以上是项目中用到的XPath 表述方法,其中,//*是对HTML 整个文本的所有标签检索出id=infopageIdentity 的所有文本信息。难点是PLM 系统中使用AJAX 来提升网页响应速度,用户需要滑动滚轮来获取所需数据,在Ajax 动态网页部分不滑动时无法定位所需元素信息。针对这个难点采用页面滚动相关的API 接口函数scrollIntoView 实现滑动,代码实现如下。
1.l2currentcr =('L2CurrentCR','')#获取当前的订单号
2.processtab =WebDriverWait(spider.brower,120).un⁃til(
3.EC.presence_of_element_located((By.XPATH,'//span [contains(text(),"Process")]'))
4.)
5.processtab.click()
#切换PLM 网页为process 进程页面
6.target =spider.brower.find_element_by_xpath(l2cur⁃rentcr)#到当前订单号的位置
<:
8.ute_script("arguments[0].scrollInto View();",target)#滑动滚轮将当前订单号置顶9.time.sleep(10)
中间件返回URL 到Spider 中callback 解析Request 请求响应指定函数直至解析完。Spider 继续构造Re⁃quest 对象发送下一个URL 请求,重复直至遍历URL 队列,完成所需网页的数据爬取。2.2.3数据处理
数据处理是指Item.py 和Pipelines.py 两个文件的代
mysql下载下来没安装包码编写,前者是简单容器来保存爬取的数据项,后者为爬取数据通往数据库提供通道,在通道里可以将数据同步或异步存储数据库。当爬虫数据量少时可采用同步存储,数据量大且爬取的速度比插入数据库的速度快时同步存储会出现堵塞,此时就需要采用异步保存。为了便于数据可持续集成及处理采用的是MySQL 开源数据库的异步存储。(1)定义数据库的host、DB 及用户名密码相关
参数:
1.MYSQL_HOST ="127.0.0.1"
2.MYSQL_DBNAME ="plm_scrapy"
3.MYSQL_USER ="root"
4.MYSQL_PASSWORD ="0987654321"
(2)在Pipelines.py 中的process_item 函数编写,是
必需不可缺少的,是在爬取过程中对数据进行处理并返回Item,代码如下:
1.def process_item(self,item,spider):
2.query =self.dbpool.runInteraction (self.do_mysql,item)
3.query.addCallback(self.handle_error)
(3)对处理异步MySQL 数据库,需引入twisted 和
pymysql 库,然后在PlmPipeline 类中新建4个功能函数,分别是:_init_()、from_settings()、do_mysql()、han⁃
dle_error()。编写_init_()函数作用是初始化参数,实现方法为:
1.from itemadapter import ItemAdapter
2.import pymysql as mysql
3.prise import adbapi
4.class PlmCrawlerPipeline:
5.def _init_(self,dbpool):
6.
self.dbpool =dbpool
编写from_settings 函数,继承settings 类获取配置
文件中的参数连接MySQL 数据库,获取游标dbpool 并返回,实现方法如下。
1.@classmethod
2.def from_settings(cls,settings):
3."""函数名固定,会被scrapy 调用,直接可用settings 的值
4.*数据库建立连接
5.*parameter:settings:配置参数
6.*return:实例化参数
7."""
8.adbparams =dict(
9.host=settings['MYSQL_HOST'],#数据库主机,ip 10.db=settings['MYSQL_DBNAME'],#待操作的数据库11.user=settings['MYSQL_USER'],#允许登录的用户名12.password=settings['MYSQL_PASSWORD'],#密码13.cursorclass=pymysql.cursors.DictCursor #指定cursor 类型14.)
15.dbpool =adbapi.ConnectionPool ('mysql',**adb⁃params)#连接数据池urn cls(dbpool)
编写do_mysql函数将数据保存到数据库中,插入数据判断是否已存在,若已在仅更新爬虫日期,若不存在插入数据,实现方法如下。
1.def do_mysql(self,cursor,item):
2.cursor=self.dbpool.cursor()
3.#1.查询数据是否存在
4.select_sql="""
5.SELECT*FROM datatable WHERE(cr_id='%s' AND cr_title='%s'AND cr_type='%s'
6.AND cr_complexity='%s'AND cr_status='%s' AND cr_modifydate='%s'AND from_cr_id='%s'
7.AND single_cr_id='%s'AND single_cr_title='%s' AND single_cr_description='%s'
8.AND single_task_owner='%s'AND single_task_sta⁃tus='%s'AND single_task_comments='%s'
9.AND single_task_from_dt='%s'AND single_task_to_ dt='%s'
10.%(item['cr_id'],item['cr_title'],item['cr_type'],item ['cr_complexity'],item['cr_status'],
11.item['cr_modifydate'],item['from_cr_id'],item['sin⁃gle_cr_id'],item['single_cr_title'],
12.item['single_cr_description'],item['single_task_own⁃er'],item['single_task_status'],
13.item['single_task_comments'],item['single_task_ from_dt'],item['single_task_to_dt']
14.)
15."""
17.#2.插入数据
18.if0==result:
19.insert_sql="""
20.INSERT INTO datatable(cr_id,cr_title,cr_type,cr_ complexity,cr_status,cr_modifydate,
21.from_cr_id,single_cr_id,single_cr_title,single_cr_ description,single_task_owner,
22.single_task_status,single_task_comments,single_ task_from_dt,single_task_to_dt)
23.VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,% s,%s,%s,%s,%s,%s)"""
ute(insert_sql,(
25.item['cr_id'],item['cr_title'],item['cr_type'],item ['cr_complexity'],item['cr_status'],
26.item['cr_modifydate'],item['from_cr_id'],item['sin⁃gle_cr_id'],item['single_cr_title'],
27.item['single_cr_description'],item['single_task_own⁃er'],item['single_task_status'],
28.item['single_task_comments'],item['single_task_ from_dt'],item['single_task_to_dt']))
29.#3.更新数据
30.else:
31.update_sql="""
32.UPDATE database SET spider_time=%s WHERE (cr_id='%s'AND cr_title='%s'AND cr_type='%s' 33.AND cr_complexity='%s'AND cr_status='%s' AND cr_modifydate='%s'AND from_cr_id='%s' 34.AND single_cr_id='%s'AND single_cr_title='%s' AND single_cr_description='%s'
35.AND single_task_owner='%s'AND single_task_sta⁃tus='%s'AND single_task_comments='%s'
36.AND single_task_from_dt='%s'AND single_task_ to_dt='%s'
37.%(item[spider_time],item['cr_id'],item['cr_title'], item['cr_type'],item['cr_complexity'],item['cr_status'], 38.item['cr_modifydate'],item['from_cr_id'],item['sin⁃gle_cr_id'],item['single_cr_title'],
39.item['single_cr_description'],item['single_task_ow-ner'],item['single_task_status'],
40.item['single_task_comments'],item['single_task_ from_dt'],item['single_task_to_dt'] 41.)"""
编写handle_error函数是异常信息处理:
1.def handle_error(self,failure):
if failure:
2.print(failure)
3分布式爬虫及呈现
实现PLM系统的爬虫和数据存储后,为了连续采集数据需定时触发爬虫任务,用Scrapyd分布式在多台电脑上爬虫的需求。主从服务器安装Scrapyd,主服务器安装Scrapydweb。在主服务电脑端在C:\Users\*路径下修改配置文件scrapydweb_settings_v10.py:
1.SCRAPYD_SERVERS=[‘127.0.0.1:6860’,
2.‘10.138.1
3.13:6868’,
3.‘10.138.13.14:6861’,]
打开从服务电脑Python安装路径下scrapyd中de⁃f文件并将bind_address修改为0.0.0.0允许任何IP访问。
先在电脑终端中输入scrapyd并回车启动,再在主电脑新打开终端输入scrapydweb并回车,“Visit Scrapyd⁃Web at 127.0.0.1:5000”表示启动成功。打开

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。