本文首发于我的博客:gongyanli.com
代码传送门:https://github.com/Gladysgong/cninfo
简书: https://www.jianshu.com/p/b5ef0e7e2b87
CSDN: https://mp.csdn.net/mdeditor/79759833
首先说说我的目标把,就是抓取巨潮资讯网上一些上市农业企业的基本信息,主要是对页面的公司概况、高管人员、十大股东这几个板块的信
息进行抓取,如图。要抓取的上市农业企业的名单已经准备好了,但是同时要拿到的这些农业企业的url地址。本来考虑的是做一个整站提取url,
但是再想一想,这个网站包含了太多上市公司的信息,即使拿到了,也需要慢慢找。加上我们要抓取的农业企业不多,所以分析页面结果后,手动
整理他们的url,算是本爬虫的一个缺陷。
巨潮资讯地址:http://www.cninfo.com.cn/information/companyinfo_n.html?brief?szmb000998
上面这个就是公司概况的url地址,而高管人员只需要把brief换成management,十大股东只需要把brief换成shareholders,而后面的后缀
szmb000998这个是手动整理的,这个szmb没看出什么意思,后面的数字是当前公司的股票代码。
经过分析,发现网页是动态加载的,里面的内容都是通过js来控制iframe进行展现的,通过scrapy中response.body获取网页的返回结果中,
没有完美所需要的内容,所以我们需要用selenium。
一、PhantomJS--PhantomJS安装验证
PhantomJS是一个基于webkit内核的没有界面的浏览器,所以它和chrome、Firefox这些没有什么差别,只是没有界面而已啦,所以并无高深之处。
关于它的安装及验证非常简单,大家可以参考我的另一篇文章,标题处去点击链接把。
二、Selenium--Selenium的使用
Selinium是一个自动化的测试工具,用它可以驱动浏览器执行特定的操作,比如点击按钮,切换到ifame中等操作,同时能够获取到浏览器渲染后的
源码。所以它对于那些用JavaScript渲染的网页来讲,Selenium是再合适不过了。
崔庆才的博客中有一篇详细讲解了Selenium的用法,可以参考。
三、通过Scrapy来使用Selenium
1.中间件
首先看一下我的工作目录把,没有什么特点,scrapy典型的工作目录,唯一不一样的是middlewares文件夹,里面存放的是我自定义的中间件。通过
自定义的中间件去把scrapy原本的中间件覆盖,从而用我们自己实现的功能去替换scrapy原有的功能。
我的中间件代码如下:打开PhantomJS浏览器,请求url地址,睡眠,接着切换iframe,因为我要获取的公司概况信息就在id='i_nr'的这个ifame
中,再睡眠,等待浏览器渲染出这个ifame中的内容,然后再body中保存此网页的源码,最后利用HtmlResponse把body传送离开。
`from selenium import webdriver
from scrapy.http import HtmlResponse
import time
class JavaScriptMiddleware(object):
def process_request(self, request, spider):
if spider.name == 'CninfoSpider':
print('PhantomJS1 is starting...')
driver = webdriver.PhantomJS(executable_path=r'E:\Program Files\phantomjs-2.1.1-windows\bin\phantomjs.exe')
driver.get(request.url)
time.sleep(1)
driver.switch_to.frame('i_nr')
time.sleep(2)
body = driver.page_source
print("访问:", request.url)
return HtmlResponse(driver.current_url, body=body, encoding='utf-8')
elif spider.name == 'CninfoManaSpider':
print('PhantomJS2 is starting...')
driver = webdriver.PhantomJS(executable_path=r'E:\Program Files\phantomjs-2.1.1-windows\bin\phantomjs.exe')
driver.get(request.url)
time.sleep(1)
driver.switch_to.frame('i_nr')
time.sleep(2)
body = driver.page_source
print("访问:", request.url)
return HtmlResponse(driver.current_url, body=body, encoding='utf-8')
else:
return
`
2.数据解析--cninfo.py
之后,我们就可以回到cninfo.py文件中对内容进行解析了。重写__init__,使用webdriver打开PhantomJS浏览器。重写start_requests方法,
拼接url,同时可以自定义返回函数parse。
`# --*-- coding:utf-8 -*-
import scrapy
from scrapy import Request
from scrapy.spiders import Spider
from selenium import webdriver
from ..items import AgriBasicItem
# 巨潮资讯网--上市农业企业基本信息
class CninfoSpider(Spider):
name = 'CninfoSpider'
def __init__(self):
self.broswer = webdriver.PhantomJS(
executable_path=r'E:\Program Files\phantomjs-2.1.1-windows\bin\phantomjs.exe')
self.broswer.set_page_load_timeout(30)
def closed(self, spider):
print('spider closed!')
self.broswer.close()
def start_requests(self):
myurls = ['szmb000998', 'szsme002041', 'szsme002772', 'szcn300087', 'szcn300189', 'szcn300511',
'shmb600108',
'shmb600313', 'shmb600354', 'shmb600359',
'shmb600371', 'shmb600506', 'shmb600598', 'shmb601118', 'szmb000592', 'szsme002200',
'szsme002679',
'shmb600265', 'szmb000735', 'szsme002234',
'szsme002299', 'szsme002321', 'szsme002458', 'szsme002477', 'szsme002505', 'szsme002714',
'szsme002746', 'szcn300106', 'szcn300313', 'szcn300498',
'shmb600965', 'shmb600975', 'szmb000798', 'szsme002086', 'szsme002696', 'szmb200992',
'szcn300094',
'shmb600097', 'shmb600257', 'shmb600467', 'szmb000711', 'szmb000713']
start_urls = [
('http://www.cninfo.com.cn/information/companyinfo_n.html?brief?' + each) for each in myurls]
for url in start_urls:
yield Request(url=url, callback=self.parse)
def parse(self, response):
item = AgriBasicItem()
item['full_name'] = response.xpath(
'//div[@class="clear2"]/div[@class="zx_left"]/div[2]/table/tbody/tr[1]/td[2]/text()').extract()[0] # 公司名称
item['en_name'] = response.xpath(
'//div[@class="clear2"]/div[@class="zx_left"]/div[2]/table/tbody/tr[2]/td[2]/text()').extract()[0] # 英文名称
item['cn_name'] = item['full_name'] # 中文名称
item['nation'] = 'china' # 国别
item['address'] = response.xpath(
'//div[@class="clear2"]/div[@class="zx_left"]/div[2]/table/tbody/tr[3]/td[2]/text()').extract()[0] # 注册地址
item['established_time'] = None # 成立时间
item['stock_time'] = response.xpath(
'//div[@class="clear2"]/div[@class="zx_left"]/div[2]/table/tbody/tr[13]/td[2]/text()').extract()[0] # 上市时间
# shareholders = scrapy.Field() # 主要股东
item['industry'] = response.xpath(
'//div[@class="clear2"]/div[@class="zx_left"]/div[2]/table/tbody/tr[8]/td[2]/text()').extract()[
0] # 行业(经营类别)
# managers = scrapy.Field() # 主要管理人员
item['parent_company'] = None # 母公司
item['subsidiaries'] = None # 子公司
item['offical_website'] = response.xpath(
'//div[@class="clear2"]/div[@class="zx_left"]/div[2]/table/tbody/tr[12]/td[2]/text()').extract()[0] # 官网
item['phone'] = response.xpath(
'//div[@class="clear2"]/div[@class="zx_left"]/div[2]/table/tbody/tr[10]/td[2]/text()').extract()[0] # 公司电话
item['fax'] = response.xpath(
'//div[@class="clear2"]/div[@class="zx_left"]/div[2]/table/tbody/tr[11]/td[2]/text()').extract()[0] # 公司传真
item['Twitter'] = None # Twitter
item['stock_code'] = response.xpath('//div[@class="zx_info"]/form/table/tbody/tr/td[1]/text()').extract()[
0] # 股票代码
item['abbr'] = response.xpath('//div[@class="zx_info"]/form/table/tbody/tr/td[1]/text()').extract()[1] # 公司简称
yield item
`
3.数据持久化--pipelines.py
把抓取下来的数据存进mongo数据库,实现数据的持久化。setting中数据库配置如下:
MONGO_HOST = '127.0.0.1' # 主机ip
MONGO_PORT = 27017 # 端口号
MONGO_DB = 'agriEn' # 数据库名称
`import pymongo
from scrapy.conf import settings
from .items import AgriBasicItem
from .items import AgriManaItem
class CninfoPipeline(object):
def __init__(self):
self.client = pymongo.MongoClient(host=settings['MONGO_HOST'], port=settings['MONGO_PORT'])
self.db = self.client[settings['MONGO_DB']]
self.cninfo_base = self.db['cninfo_base']
self.cninfo_mana = self.db['cninfo_mana']
self.cninfo_share = self.db['cninfo_share']
def process_item(self, item, spider):
if isinstance(item, AgriBasicItem):
try:
if item['full_name']:
item = dict(item)
self.cninfo_base.insert(item)
print("insert baseinfo success !")
return item
except Exception as e:
spider.logger.exception("insert failed")
elif isinstance(item,AgriManaItem):
if item['managers']:
item = dict(item)
self.cninfo_mana.insert(item)
print("insert managers success !")
return item`
4.配置文件--setting.py
ROBOTSTXT_OBEY = False
DOWNLOADER_MIDDLEWARES = {
# 键是中间件类的路径,值是中间的顺序
'cninfo.middlewares.middleware.JavaScriptMiddleware': 543,
# 禁止内置的中间件
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None }
# 记得开启pipelines的调用
ITEM_PIPELINES = {
'cninfo.pipelines.CninfoPipeline': 300,}
四、缺陷
在这个爬虫中,其实出现了蛮多问题的,有些解决了,有些没有,记录一下,加深印象,还有感觉代码好垃圾。
1.缺陷1
上市农业企业的名字,以及名字的url链接手动整理的,因为只有40来个企业,所以才会用这个方法。本来是想通过Scarpy的CrawlSpider抓取,
抓取时通过Rule来控制所需要的农业企业,但是发现实在没有什么规律,只会把巨潮资讯上所有上市企业的url都获取下来,所以最后手动整理的
这40多个农业企业,希望日后能找到更简便的办法。
2.缺陷2
对于网页中的高管人员信息抓取中,本来是想思考放在一个爬虫中获取到的,但是仔细分析后发现,公司概况和高管人员分别对应着不同的url,
然后不同的url下再对应中动态加载iframe。url对比如下:
http://www.cninfo.com.cn/information/companyinfo_n.html?brief?szmb000998
http://www.cninfo.com.cn/information/companyinfo_n.html?management?szmb000998
这样子的话,就涉及到我需要在Selenuim中点击按钮,然后再渲染id='i_nr'的iframe,而且不同页面中iframe名字都叫i_nr。我试过这个
办法,但是拿回的源码并不是高管人员页面的源码,依然是公司概况页面的源码,也许是我方法用的不对,有待仔细研究。
所以我又在cninfo_mana.py中又写了一个爬虫,从而单独来获取高管人员页面的信息。
3.缺陷3
对于十大股东页面,按理说我应该在写一个py文件来单独获取它的页面信息,这样子就ok了,实际上我也是这么做的。但是爬虫运行是,却发
现我拿不到股东的信息,因为我发现当请求http://www.cninfo.com.cn/information/companyinfo_n.html?shareholders?
szmb000998时,首先切换到id='i_nr'的iframe中,但是随后股东的详细信息又在一个id='i_nr'的ifrma中,相当于这里有两个iframe,
我在中间件试了试两个这样子切换iframe,但是很遗憾我没拿到我想要的东西,源代码还是停留在第一层iframe中,所以最后我放弃了,没有
拿十大股东的信息,这个问题智能留着我以后解决了。
4.缺陷4
self.broswer = webdriver.PhantomJS(
executable_path=r'E:\Program Files\phantomjs-2.1.1-windows\bin\phantomjs.exe')
这个代码在中间件和爬虫代码中都有,说明两次打开了浏览器,致使性能降低。
五、其他项目
其实我之前还用Selenium写过一个项目,用过抓取FAO的国家分类信息,以前习惯不好,做事不记录,导致做完就忘,只有个模糊的印象。
今天把之前那个项目找到了,重新运行了一下,但是抓不到东西了,我看看了网站的结构没变,应该是在动态加载方面变化了,等有时间的时候
看看究竟哪里变了,再把代码优化一下,附上github地址,有兴趣可以参考一下。如果有帮助,给个star把,好不要脸,第一次求star。
https://github.com/Gladysgong/fao
六、后续
后来我又写了一篇文章——用Python下载巨潮资讯农业上市企业的年报PDF文件(二),有兴趣的可以看看。