案例中将展示机场官网中航班信息(如机场航班的离港与进港信息)的爬取过程。有兴趣的读者可以在本案例的基础上对数据进一步分析,或是对爬虫做进一步的开发,增加更多功能。
请求、解析、处理数据是通用爬虫的三个步骤,在本案例中,利用机场官网的详细信息,在网页上定位各类数据的路径,通过Scrapy爬取得到对应的数据,最后将多个数据统筹整合进一个JSON文件,最终得到机场航班的相关信息。
打开机场官网(详见前言中的二维码),以进港航班信息为例,存储航班信息的详细页面如图1所示。
■ 图1 机场进港航班数据列表
按F12键打开浏览器的调试模式,可以通过Elements来定位当前页面中数据的存放位置,样例如图2所示。Elements中的元素比较复杂,所以本次爬虫决定使用Scrapy框架直接获取网页的页面内容,通过element的XPath来精准定位需要爬取的数据。
■ 图2 数据存放位置的样例图
该爬虫使用了Scrapy框架,读者需预先安装Python发行版,并在终端中输入pip install scrapy指令来安装Scrapy框架。
Scrapy框架的目录层次如图3所示。
■ 图3 Scrapy框架的目录层次
比较重要的是items.py、settings.py、pipelines.py、middlewares.py以及spiders文件夹中放置的爬虫主程序。在settings.py文件中,可以对框架进行一些设置。例如,是否使用中间件、是否使用管道等。items.py文件是一个对象文件,用于指示本次爬虫爬取对象的结构,以存放爬取到的数据,下面以TrafficItems.py文件为例,来看一看该内容需要如何实现。
【例1】指示爬取对象的结构,将爬取到的数据保存至TrafficItems.py文件。
import scrapy
class TrafficItems(scrapy.Item):
sysmbol = scrapy.Field() #标识符
model = scrapy.Field()
leavePlace = scrapy.Field() #始发地
leaveAirport = scrapy.Field() #出发机场
destssination = scrapy.Field() #目的地
destssinationAirport = scrapy.Field() #到达机场
flightTime = scrapy.Field() #起飞时间
flightTimeEx = scrapy.Field() #起飞时区
arrivedTime = scrapy.Field() #到达时间
arrivedTimeEx = scrapy.Field() #到达时区
pass
构造对象的过程利用了Python面向对象写法,在这里需要引用Scrapy库,使用Field是为了以下两点。
(1) Field对象指明了每个字段的元数据(任何元数据),Field对象接收的值没有任何限制。
(2) 设置Field对象的主要目的就是在一个地方定义好所有的元数据。
MySpiders.py文件位于文件Spiders下,该文件中存放了提取数据的Spider,它用于定义初始URL根网址、请求初始URL之后的逻辑,以及从页面中爬取数据的规则(即写正则或xpath等)。
【例2】执行Spider,爬虫获取数据MySpider.py主文件。
import scrapy
from yryProject.TrafficItems
import TrafficItems
class MySpider(scrapy.Spider):
name ="MySpider"allowed domains =['flightaware.com']start_urls =['https://zh, flightaware. com/live/airport/RJAA/arrivals?;offset = 0; sort =ASC;order =actualarrivaltime']
def parse(self,response):
items =targe
tFile = open("target.html",w',encoding = "utf - 8")targetFile.write(str(response.body))for box in response.xpath( "//*[@ d ='slide0utPanel']/div[1]/table[2]/tbody/trtdl11/table/tbody/tr") :
item = TrafficItems()
Symbol = box.xpath("./td[1]/span/a/text()").extract()
model = box.xpath("./td[ 2]/span/a/text()").extract()
leavePlace = box.xpath("./td[3]/span[1]/span/text()").extract()
landPlace = box.xpath("./td[3]/span[1]/span/text()").extract()leaveAirport = b ox.xpath("./td[3]/span[2]/a/text()").extract()#landAirport = box.xpath("./td[3]/span[2]/a/text()").extract()
flyTime = box.xpath("./td[ 4]/text()").extract()
flyTimeEx = box.xpath("./td[4]/span/text()").extract()
arriveTime = box.xpath("./td[ 5]/text()").extract()
arriveTimeEX = box.xpath("./td[5]/span/text()").extract()
if (Symbol !=[]):
item['Symbol =Symbol[ 0]
else:
item['Symbol'] = unknown'
if (model !=[]):
item['model'] = model[0]
else:
item['model'] =unknown'if (leavePlace != []):item['leavePlace'] = leavePlace[0]
else:
item['leavePlace'] = unknown'
if (leaveAirport !=[]):
item[leaveAirport'] = eaveAirport[0]else:
item[leaveAirport'] = unknownif (flyTime != []):
item['flightTime'] = flyTime[0]else:
item['flightTime'] = unknownif (flyTimeEx != []):
item['flightTimeEx'] = flyTimeEx[0]else:
item['flightTimeEx'] = unknown'if (arriveTime !=[]):
item['arrivedTime'] = arriveTime[0]else:
item['arrivedTime']"'unknownif (arriveTimeEX != []):
item[arrivedTimeEx'] = arriveTimeEX[0]
else:
itemarrivedTimeEx'] = unknown!yield item
response. xpath( "/html/body/div[1]/div[1]/table[ 2]/tbody/tr/td[1].next url =
span[2]/a[1]/@href").extract first()print(next url)yield scrapy.Request(next url,callback = self.parse)
#return items
在Spider文件中,需要定义一个爬虫名与爬取的网址的域名(allowed_domains),首先需要给出一个起始地址(start_urls),然后Scrapy框架就可以根据这个起始地址获取网页源代码,通过源代码获取所需值。下面通过XPath定位对应数据所在的HTML位置,再存放至item中构造好的对应字段中。这样就完成了一次爬取。
此处,一次性爬取一个页面的完整内容后,对于后续页面,可以通过next_url存放下一页面的网址。在爬取完后通过yield scrapy.Request(next_url,callback=self.parse)定位下一页,从而实现模拟翻页。
Mypipeline.py文件是一个管道文件,它约定了爬虫爬取后的内容流向哪里,由于需要存储这些爬取后的文件,所以在管道中将其写入了一个JSON文件。Item在Spider中被收集之后,它将会被传递到Item Pipeline,Pipeline接收到Item并通过它执行一些行为,同时也决定此Item是否能继续通过Pipeline,或是被丢弃。例3为Mypipeline.py的详细代码。
【例3】约定爬取到的内容如何处理。
#Mypipeline.py
#引入文件
from scrapy.exceptions import DropItem
import json
class MyPipeline(object):
def init (self):
# 打开文件
self.file = open('data.json','w',encoding ='utf - 8')
# 该方法用于处理数据
def process item(self,item,spider):
#读取 item 中的数据
line = json.dumps(dict(item),ensure ascii= Ealse)
#写入文件
self.file.write(line)
#返回 item
return item
#该方法在 spider 被开启时被调用def open spider(self,spider):
pass
#该方法在 spider 被关闭时被调用def close spider(self,spider):
pass
由于案例中爬取数据所用的浏览器为Chrome,本爬虫使用了ChromeDriver。当编写此爬虫时, Chrome版本为85.0.4183.83,所使用的ChromeDriver需要和Chrome版本相匹配,则需要在chromeMiddleware.py中将driver初始化的指向路径改为本机中存放ChromeDriver的绝对路径。
【例4】设置ChromeDriver启动参数的中间件配置文件。
# chromeMiddleware.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from scrapy.http import HtmlResponse
import time
class chromeMiddleware(object):
def process request(self,request,spider):if spider.name == "MySpider":chrome options = Options()chrome options.add argument('--headless
chrome options.add argument('--disable -gpu')
chrome options.add argument('-- no -sandbox')
chrome options.add argument('-- ignore- certificate-errors')
chrome options.add argument('-- ignore - ssl - errors')
driver = webdriver.Chrome( "E: scrapy_work yryProject\yryProject chromedriverexe",chrome options = chrome options)driver.get(request.url)body = driver.page source#print("访问”+ request.url)return HtmlResponse(driver.current url, body= body, encoding = UTF - 8,request:
request
else:
return None
在爬虫运行的过程中,需要对一些细节进行设定,如设定爬虫运行的持续时间、限制爬取内容的数量,或是对爬虫延迟做出调整,这些需要调整的变量都存放于settings.py中,代码如例5所示。
【例5】设置爬虫通用属性的文件。
# settings.py
import uagent
BOT NAME =yryProject'
SPIDER MODULES =['yryProject.spiders']NEWSPIDER MODULE = yryProject.spidersCLOSESPIDER PAGECOUNT = 4
FEED EXPORT ENCODING =UTF - 8
ROBOTSTXT OBEY = False
USER AGENT = uagent.randomUserAgent()
DEFAULT REOUEST HEADERS =
Accept':'text/html,application/xhtml + xml,application/xml;q= 0.9,*/*;q= 0.8''Accept - Language':'en'
DOWNLOADER MIDDLEWARES = yryProject.chromeMiddleware.chromeMiddleware': 543
ITEM PIPELINES = (
yryProject.MyPipeline.MyPipeline': 1
}
修改完地址后,进入\yryProject目录,执行scrapy crawl MySpider即可运行爬虫。
爬取的结果保存在data.json文件中,其截图如图4所示。
■ 图4 爬取结果展示