发现问题
早些时候,笔者初学网络爬虫,想要做一个小爬虫,小试牛刀。分析网页时,用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,大概率爬取不到东西
复制代码- # 开启ITEM_PIPELINES,使其可以ITEM_PIPELINES处理item数据
复制代码- 'GoWhere.pipelines.GowherePipeline': 300,
复制代码- # 'Douban.middlewares.DoubanSpiderMiddleware': 543,
复制代码- 'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
复制代码- [/code][code]#设置渲染服务的url,这是js渲染的关键之所在,这里的url即为刚刚开启Docker的Ip加splash服务指定的端口
复制代码- SPLASH_URL="http://192.168.99.100:8050"
复制代码- #这里配置了三个下载中间件( 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,
复制代码- [/code][code]HTTPCACHE_ENABLED = True
复制代码- HTTPCACHE_EXPIRATION_SECS = 0
复制代码- HTTPCACHE_DIR = 'httpcache'
复制代码- DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
复制代码- HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
复制代码 接下来,编写items.py文件
- class GowhereItem(scrapy.Item):
复制代码- time_from = scrapy.Field()
复制代码 然后,爬虫的核心,编写spider,请求网页,清洗数据。强调一点,start_request需要重写,而且请求方法必须使用SplashRequest,否则前功尽弃。:
- from scrapy_splash import SplashRequest
复制代码- from GoWhere.items import GowhereItem
复制代码- [/code][code]class GowhereSpider(scrapy.Spider):
复制代码- 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']
复制代码- [/code][code] 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})
复制代码- [/code][code] def parse(self, response):
复制代码- results = response.xpath("//div[@class='js_listinfo']")
复制代码- 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['time_from'] = time_form
复制代码- list['time_to'] = time_to
复制代码- ok = result.xpath('./div[5]/p[3]/text()').extract_first()
复制代码- 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()
复制代码- flag = result.xpath('./div[5]/p[4]/text()').extract_first()
复制代码- 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()
复制代码- [/code]最后,那就是编写Item pipelines,对item数据进行储存,笔者在这里使用本地储存(指定文件格式为csv)和mysql存储,代码具体如下:
- [list][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][/list][code]class GowherePipeline(object):
复制代码- # 以写模式打开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()
复制代码- [/code][code] self.db = connect('localhost','root','1234','book',charset = 'utf8')
复制代码- [/code][code] 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'])
复制代码- [/code][code] def spider_close(self):
复制代码 数据效果展示:
![]()
至此,笔者要跟你们说再见了。
初来乍到,编写文章经验不足,还望多多关照,如有阐述不清楚请下方留言。。。
|
|