发现问题
早些时候,笔者初学网络爬虫,想要做一个小爬虫,小试牛刀。分析网页时,用Chrome或者FireFox浏览器(个人推荐用FireFox,抓包效果更好)获取网页数据进行分析,这时数据都是非常完整的,如下图所示:
然后屁颠颠去写代码,什么requests、urllib的各种各样的库,再用xpath、正则表达式、beautifulsoup去解析网页,结果发现屁数据也没有,如下图所示:
然后就只能辛辛苦苦地去抓包,看数据都放哪儿了,而且还不一定能找到,简直无奈啊,真是气skr人。
找到原因
这种网站的内容由前端的JS动态生成,由于呈现在网页上的内容是由JS生成而来,我们能够在浏览器上看得到,但是在HTML源码中却发现不了。
解决办法
有三种方法,分别是:
(1)Selenium+webdriver(Chrome或者FireFox):这要求你系统有对应浏览器,并且过程中要全程开浏览器。说白了,就是你通过浏览器能看到啥,就能抓到啥。一般遇到特别复杂的验证码时,这个方法是有必要的,当然,开着浏览器爬虫的效率可想而知。
(2)selenium+phantomjs。PhantomJS是一个WebKit,他的使用方法和webdriver一样,但是他不需要开浏览器,简单地讲,它PhantomJS就是一个无界面的浏览器,你可以直接跑在无需GUI的linux服务器上,这点很赞。
(3)第三种方法就是本文主要阐述的内容,那就是基于Docker容器的scrapy-splash JS渲染服务和python分布式爬虫框架Scrapy,scrapy-splash作为js渲染服务,是基于Twisted和QT开发的轻量浏览器引擎,并且提供直接的http api,快速、轻量的特点使其容易进行分布式开发。 splash和scrapy融合,两种互相兼容彼此的特点,抓取效果甚佳。
那么接下来笔者就手把手教大家第三种方法
首先,安装Docker容器,以及安装、开启Splash服务
如何安装笔者就不详述,自行百度教程,非常简单。安装好Docker,进入有小鲸鱼的终端,输入如下指令安装splash服务(安装过程比较慢,请大家耐心等待)
dock pull scrapinghub/splash
如下图:
安装成功后,输入如下指令开启splash(指定服务端口为8050):
docker run -p 8050:8050 scrapinghub/splash
编写爬虫
笔者在这里以爬取去哪儿旅行官网南昌到杭州的火车票的详细数据,使用的开发工具为pycharm,指定框架为Scrapy。
首先,我们配置settings.py一些重要参数,如下代码,相应的参数解释已在代码中注明。设置渲染服务的url一定不能写错,协议(http),IP(开启Docker默认的IP),端口号(8050,开启splash服务指定的端口号),如果写错,电脑会积极拒绝。
# 最好将机器协议修改为False,如果为true,大概率爬取不到东西
ROBOTSTXT_OBEY = False
# 开启ITEM_PIPELINES,使其可以ITEM_PIPELINES处理item数据
ITEM_PIPELINES = {
'GoWhere.pipelines.GowherePipeline': 300,
}
# 设置爬虫中间件
SPIDER_MIDDLEWARES = {
# 'Douban.middlewares.DoubanSpiderMiddleware': 543,
'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}
#设置渲染服务的url,这是js渲染的关键之所在,这里的url即为刚刚开启Docker的Ip加splash服务指定的端口
SPLASH_URL="http://192.168.99.100:8050"
# 添加Splash中间件,指定相应的优先级
#这里配置了三个下载中间件( DownloadMiddleware),是scrapy-splash的核心部分,我们不需要
#像对接selenium那样自己定制中间件,scrapy-splash已经为我们准备好了,直接配置即可
DOWNLOADER_MIDDLEWARES = {
# 'Douban.middlewares.DoubanDownloaderMiddleware': 543,
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
}
HTTPCACHE_ENABLED = True
HTTPCACHE_EXPIRATION_SECS = 0
HTTPCACHE_DIR = 'httpcache'
# 去重过滤器
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
#使用splash的http缓存
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
接下来,编写items.py文件
class GowhereItem(scrapy.Item):
# 车次
train = scrapy.Field()
# 起始站
where = scrapy.Field()
# 终点站
to = scrapy.Field()
# 发车起始时间
time_from = scrapy.Field()
# 到站时间
time_to = scrapy.Field()
# 乘车时间
time = scrapy.Field()
# 票价详情
price1 = scrapy.Field()
num1 = scrapy.Field()
price2 = scrapy.Field()
num2 = scrapy.Field()
price3 = scrapy.Field()
num3 = scrapy.Field()
price4 = scrapy.Field()
num4 = scrapy.Field()
然后,爬虫的核心,编写spider,请求网页,清洗数据。强调一点,start_request需要重写,而且请求方法必须使用SplashRequest,否则前功尽弃。:
import scrapy
from scrapy_splash import SplashRequest
from GoWhere.items import GowhereItem
import re
class GowhereSpider(scrapy.Spider):
name = 'gowhere'
allowed_domains = ['qunar.com']
start_urls = ['https://train.qunar.com/stationToStation.htm?fromStation=%E5%8D%97%E6%98%8C&toStation=%E6%9D%AD%E5%B7%9E&date=2019-07-27']
def start_requests(self):
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'}
for url in self.start_urls:
yield SplashRequest(url,callback=self.parse,headers = headers ,args={"wait":5})
def parse(self, response):
# 获取列车所有信息列表
results = response.xpath("//div[@class='js_listinfo']")
for result in results:
list = GowhereItem()
# 获取车次
train = result.xpath('./div[1]/h3/text()').extract_first()
# 获取出发站
where = result.xpath('./div[2]/p[1]/span/text()').extract_first()
# 获取终点站
to = result.xpath('./div[2]/p[2]/span/text()').extract_first()
# 发车时间
time_form = result.xpath('./div[3]/time[1]/text()').extract_first()
# 到站时间
time_to = result.xpath('./div[3]/time[2]/text()').extract_first()
# 乘车时间
time = result.xpath('./div[4]/time/text()').extract_first()
# 票价
price1 =result.xpath("./div[5]/p[1]/text()").extract_first() +" ¥" + result.xpath('./div[5]/p[1]/span/text()').extract_first()
num1 = result.xpath('./div[6]/p[1]/text()').extract_first()
price2 =result.xpath('./div[5]/p[2]/text()').extract_first() +" ¥" + result.xpath('./div[5]/p[2]/span/text()').extract_first()
num2 = result.xpath('./div[6]/p[2]/span/text()').extract_first()
list['train'] = train
list['where'] = where
list['to'] = to
list['time_from'] = time_form
list['time_to'] = time_to
list['time'] = time
list['price1'] = price1
list['num1'] = num1
list['price2'] = price2
list['num2'] = num2
ok = result.xpath('./div[5]/p[3]/text()').extract_first()
if ok:
price3 = result.xpath('./div[5]/p[3]/text()').extract_first()+ " ¥" + result.xpath(
'./div[5]/p[3]/span/text()').extract_first()
num3 = result.xpath('./div[6]/p[3]/span/text()').extract_first()
list['price3'] = price3
list['num3'] = num3
flag = result.xpath('./div[5]/p[4]/text()').extract_first()
if flag:
price4 = result.xpath('./div[5]/p[4]/text()').extract_first() + " ¥" + result.xpath(
'./div[5]/p[4]/span/text()').extract_first()
num4 = result.xpath('./div[6]/p[4]/text()').extract_first()
list['price4'] = price4
if num4:
list['num4'] = num4
yield list
最后,那就是编写Item pipelines,对item数据进行储存,笔者在这里使用本地储存(指定文件格式为csv)和mysql存储,代码具体如下:
class GowherePipeline(object):
def __init__(self):
# 以写模式打开csv格式的文件,若不存在,代码会自动创建该文件,利用第三个参数将把写入的数据项产生的空格删除
self.file = open("Go.csv",'w',newline='',encoding="utf-8")
# 设置文件第一行的名称,即数据列名称,要与spider传过来的字典key名称一致
self.fieldnames = ['train','where','to','time_from','time_to','time','price1','num1','price2','num2','price3','num3','price4','num4']
# 指定文件的写入方式为csv字典写入,参数1为指定具体文件,参数2为指定字段名
self.writer = csv.DictWriter(self.file,fieldnames=self.fieldnames)
# 写入第一行数据(列名称)
self.writer.writeheader()
self.db = connect('localhost','root','1234','book',charset = 'utf8')
def process_item(self, item, spider):
self.writer.writerow(item)
print(type(item['train']))
print(type(item['where']))
cursor = self.db.cursor()
sql = """ INSERT INTO test(train,address) VALUES ('{}','{}')""".format(item['train'],item['where'])
cursor.execute(sql)
self.db.commit()
print("数据存储成功")
def spider_close(self):
self.file.close()
self.db.close()
最后那就是输入指令运行爬虫了:
scrapy crawl gowhere
如有阐述不清楚的地方,欢迎来我的订阅号提问,个人订阅号:鱼少侠