环境配置
- Ubuntu 16.04
- Python 3.5
技术框架
- Scrapy
- Selenium
- PhantomJS
需求目标
对网址http://liuyan.cjn.cn/index.html
进行数据爬取
本文爬虫有三层爬取,爬取结构层次如图所示,
- 主页面
- 列表页面,需要利用
selenium+phantomJS
模拟点击加载更多
按钮
- 详情页面
- 把
主页面
中的红框中的各区(功能区)
、政府部门
、法检及其他单位
等作为一级类保存,然后依次爬取一级类中各个分类 - 进入到
列表页面
,因为此页面是动态加载,并且是简介列表,因此需要加载完所有列表进入到详情页面。 - 进入到
详情页面
,爬取相应内容。
设计方案
动态爬取数据一般有两种方法
- 分析包,构造Request
- 通过模拟浏览器行为加载页面
通过尝试,第一种方法对于此网站不适合,采用第二种方法。
采用Scrapy+Selenium+PhantomJS
来进行动态网页的爬取。
Selenium
Selenium是一个自动化测试工具,本文用到的是其Webdriver
,用其来操作浏览器,可以操作多种主流浏览器,如Chrome,Firefox等,下文中的PhantomJS
是一种无界面的浏览器。
PhantomJS
PhantomJS是一个无界面浏览器,提供JavaScript接口。通过这个工具,可以进行页面的加载、解析、操作等。
爬取方案
- 先爬取
主页面
各个主类(如各区(功能区)
、政府部门
等)下的子类链接,如(江岸区、江夏区等) - 按照先前爬取的子类链接依次打开跳转到
列表界面
,然后利用Selenium+PhantomJS
模拟浏览器动态加载完全部列表,解析出所有列表的链接,跳转到详情页面
- 进入到
详情页面
,解析所要提取的内容
详细步骤
安装Scrapy
sudo pip3 install scrapy
安装PhantomJS
- 安装PhantomJS
sudo apt-get install phantomjs
- apt-get会安装不完整,需要安装以下
sudo apt-get install nodejs
sudo apt-get install nodejs-legacy
sudo apt-get install npm
sudo npm -g install phantomjs-prebuilt
安装Selenium
sudo pip3 install selenium
使用Scrapy
- 创建新工程
scrapy startproject xxxx
- 编辑
items.py
文件
from scrapy import Item
from scrapy import Field
class MessageboardspiderItem(Item):
# define the fields for your item here like:
dir_name=Field()
url=Field()
title=Field()
domain=Field()
type=Field()
content=Field()
- 编写Spider
流程:
先通过scrapy爬取
主页面
上的所有子分类链接,然后依次利用
PhantomJS
模拟点击列表页面
的加载更多
按钮,动态加载所有列表项,然后保存网页源码,抽取出其中的列表项的链接,进入详情页面
此两过程的代码都在
parse(self,response)
方法中通过
parse_detailed_remark(self,response)
方法,解析出需要的内容
【注】目前版本爬取主页面
上不同的主类需要手动改动baseDir
与self.start_urls
import os
import stat
import scrapy
import time
from selenium import webdriver
from scrapy.selector import Selector
from scrapy.http import HtmlResponse
from messageBoardSpider.items import MessageboardspiderItem
baseDir = '/home/t/dataset/msgBoard/department/' #爬取不同主分类,需要手动改动
baseUrl='http://liuyan.cjn.cn'
global subclass_name
global brower
class DistrictSpider(scrapy.Spider):
name='spider'
def __init__(self):
global brower
self.start_urls=['http://liuyan.cjn.cn/forum/list?fid=4'] #爬取不同主分类,需要手动改动
brower=webdriver.PhantomJS() #创建PhantomJS浏览器
def parse(self,response):
global brower
global subclass_name
subclasses=response.xpath('/html/body/div[7]/ul/li/b/a') #解析出所有子分类
for subclass in subclasses: #逐条解析子分类
subclass_name=subclass.xpath('text()').extract() #解析
#make district directory
self.mkdirs(baseDir+subclass_name[0])
#extrat new link
link=subclass.xpath('@href').extract() #解析出子类的链接
#joint link
link=str(baseUrl+link[0].split('..')[1]) #拼接链接
brower.get(link) #利用PhantomJS打开链接
#进行动态解析,直到没有'加载更多'按钮之后停止
while True:
try:
more=brower.find_element_by_id('show_more') #找到'加载更多'按钮
time.sleep(0.5)
more.click()
time.sleep(0.5)
except:
with open(baseDir+subclass_name[0]+'/web_page.txt','w',encoding='utf-8') as f:
f.write(brower.page_source)
print('ending')
break
#对加载完后的源码进行解析,解析出列表标题
items=brower.find_elements_by_xpath('//*[@id="list_content"]/li/h2/b/a')
for item in items:
#解析出列表标题链接
title_link=item.get_attribute('href')
print(title_link+'.........')
yield scrapy.Request(url=title_link,callback=self.parse_detailed_remark)
#解析详情页面
def parse_detailed_remark(self,response):
global subclass_name
item=MessageboardspiderItem()
item['dir_name']=subclass_name[0]
item['url']=response.url
item['title']=response.xpath('/html/body/div[6]/h2/b/text()').extract()
item['domain']=response.xpath('/html/body/div[6]/h3/em[1]/a/text()').extract()
item['type']=response.xpath('/html/body/div[6]/h3/em[2]/a/text()').extract()
item['content']=response.xpath('//*[@id="zoom"]/text()').extract()
yield item
def mkdirs(self,path):
if not os.path.exists(path):
os.makedirs(path)
os.chmod(path,stat.S_IRWXU|stat.S_IRWXG|stat.S_IRWXO)
- 编写pipelines
import json
import codecs
baseDir = '/home/t/dataset/msgBoard/department/'
class DistrictPipeline(object):
def process_item(self, item, spider):
with codecs.open(baseDir+item['dir_name']+'/data.json','a+',encoding='utf-8') as f:
self.file=f
line=json.dumps(dict(item),ensure_ascii=False)+'\n'
self.file.write(line)
return item