Scrapy框架(6):将Selenium集成至Scrapy中
一、总体功能概述
在日常学习过程中发现并非所有网页都能通过Scrapy抓取,原因在于JavaScript动态渲染,使用Selenium模拟浏览器爬取,无需关心后台请求,也不需要分析渲染过程,只要网页最终页面上能看到的内容均可抓取。本次实验所用的网页是链家网,利用Scrapy+Selenium,抓取数据的内容为昆明二手房房源信息,包括房源所在小区名称、地区、朝向、面积、结构、售价以及单位售价。将数据存入MySQL数据库,分组统计不同地区二手房数量,并筛选出总数大于一百的地区,利用pyecharts绘制柱状图实现数据可视化。最终以此为依据分析不同地区的经济发展水平、人口流动量等信息。
二、具体实施步骤
1、安装chromdriver,并配置环境变量
(1)查看chrome版本(chrome://version/)
(2)安装对应版本的驱动器chromdriver,选择对应的版本以及对应操作系统(64位操作系统是兼容32位的)
(3)配置环境变量
(4)安装Selenium,测试chromdriver
2、构建基本框架
创建虚拟环境,进入虚拟环境安装需要的库,创建工程并进入工程创建爬虫(以上操作均在黑窗口完成),新建main.py作为入口文件。
scrapy框架(2):入口文件
3、将Selenium集成到Scrapy中(下载中间件middlewares.py)
每发送一个请求都会走下载中间件,为了把Selenium集成到Scrapy中,需要在这个部分做一些拦截,使Selenium根据对应的url访问网页,并将结果响应回框架(在爬虫逻辑解析数据),不再使用下载器下载。
4、打开、关闭浏览器(爬虫逻辑)
打开浏览器很容易,但是在什么时候关闭浏览器?于是引入了Signals信号,产生信号通知事情的发生,可以通过捕捉相应的信号来做对应的操作。
Scrapy官方文档Signals页
5、解析数据(爬虫逻辑)
Parse为解析函数,利用CSS选择器筛选目标数据,即房源所在小区名称、地区、朝向、面积、结构、售价以及单位售价,利用zip将每一条房源的信息打包为一个元组,通过引擎传入pipelines.py进行数据处理等操作。解析函数的目的除了获取目标数据之外,还需要获取下一页的url。了解Scrapy的原理每一次解析的response都有对应的url,在此基础上通过字符串的拼接,获得新的url,并将其传回爬虫逻辑。
scrapy框架(3):CSS选择器解析数据
6、存入数据库(pipelines.py)
使用pymysql,连接MySQL和pycharm。pymysql的使用(pycharm与mysql的连接)
7、查找用于绘制柱状图的数据
按地区分组筛选二手房总数大于一百的数据。
8、pyecharts绘图
Scrapy框架(5):翻页操作、数据库存储以及简单的数据可视化
三、具体代码
1、入口文件
import os # 用os模块定位到当前的目录
import sys
from scrapy.cmdline import execute
sys.path.append(os.path.dirname(os.path.abspath(__file__))) # __file__为当前文件
execute(["scrapy","crawl","LianjiaSpider"])
2、爬虫逻辑
from urllib.parse import urljoin
import scrapy
from scrapy import signals
from selenium import webdriver
from FinalProject.items import FinalprojectItem
class LianjiaspiderSpider(scrapy.Spider):
name = 'LianjiaSpider'
allowed_domains = ['lianjia.com']
start_urls = ['https://km.lianjia.com/ershoufang/pg1/']
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
spider = super(LianjiaspiderSpider, cls).from_crawler(crawler, *args, **kwargs)
spider.chrome=webdriver.Chrome()
crawler.signals.connect(spider.spider_closed, signal=signals.spider_closed)
return spider
def spider_closed(self, spider):
spider.chrome.quit()
print("爬虫结束!!!!!!")
def parse(self, response):
name=response.css("div.positionInfo a:nth-child(2)::text").extract() # 小区名称
price = response.css("div.totalPrice span::text").extract() # 每套房的售价
message = response.css("div.houseInfo::text").extract() # 每套房的相关信息
position=response.css("div.positionInfo a:nth-child(3)::text").extract() # 区名
uniprice=response.css("div.unitPrice span::text").extract() # 每平米单价
room_nums=[] # 房子是几室几厅
area=[] # 房子的面积
ori=[] # 房子的朝向
for num in message:
room_nums.append(num.split("|")[0])
area.append(num.split("|")[1])
ori.append(num.split("|")[2])
Infos=zip(name,price,room_nums,area,ori,position,uniprice)
finalprojectItem=FinalprojectItem()
for Info in Infos:
finalprojectItem["name"] = Info[0]
finalprojectItem["price"] = Info[1]
finalprojectItem["room_nums"] = Info[2]
finalprojectItem["area"] = Info[3]
finalprojectItem["ori"] = Info[4]
finalprojectItem["position"]=Info[5]
finalprojectItem["uniprice"] = Info[6]
yield finalprojectItem
next=response.url
next_page=int(next.strip('/').split('pg')[1]) + 1
url=urljoin(response.url,"/ershoufang/pg"+str(next_page))
if next_page==101:
return 0
yield scrapy.Request(url, callback=self.parse) # 回调和调用的区别 parse()和parse
3、items.py
import scrapy
class FinalprojectItem(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
position = scrapy.Field()
uniprice = scrapy.Field()
room_nums =scrapy.Field()
area = scrapy.Field()
ori = scrapy.Field()
pass
4、下载中间件
from scrapy.http import HtmlResponse
class SeleniumMiddleware:
def process_request(self, request, spider):
url = request.url
# self.chrome.get(url)
# html=self.chrome.page_source
# print(html)
spider.chrome.get(url)
html=spider.chrome.page_source
return HtmlResponse(url=url,body=html,request=request,encoding="utf-8")
5、pipelins.py
import pymysql
class FinalprojectPipeline:
def __init__(self): # 连接到数据库
self.conn = pymysql.connect(host='127.0.0.1', database='lianjia', user='root', password='123456')
self.cursor=self.conn.cursor()
def process_item(self, item, spider):
with open("ershoufang.json","a+")as f: # 前端后端数据的交换格式
f.write(str(item._values))
insert_sql = "insert into info (name,price,room_num,area,ori,position,uniprice) values (%s,%s,%s,%s,%s,%s,%s)"
# 执行插入数据到数据库操作
self.cursor.execute(insert_sql, (item['name'], item['price'], item['room_nums'], item['area'],item['ori'],item['position'],item['uniprice']))
# 提交,不进行提交无法保存到数据库
self.conn.commit()
return item
6、可视化.py
import pymysql
def select():
nums = []
positions=[]
connect = pymysql.connect(host='127.0.0.1', database='lianjia', user='root', password='123456')
sql = "select position,count(*) from info group by position having count(*)>100"
cursor = connect.cursor()
cursor.execute(sql)
for row in cursor.fetchall():
position = row[0]
num = row[1]
nums.append(num)
positions.append(position)
cursor.close()
connect.close()
# print(positions)
# print(nums)
return positions,nums
7、pyecharts制图
#coding=gbk
from pyecharts.charts import Bar #柱状图的
from pyecharts import options #标题设置的
from FinalProject.可视化 import select
position,num = select() #获取数据库数据
bar = Bar()
#添加标题
bar.set_global_opts(
title_opts=options.TitleOpts(title="柱状图",subtitle="各地区二手房分布"),
)
#x轴显示地区
bar.add_xaxis(position)
#y轴显示数量
bar.add_yaxis("数量",num)
# 生成的html文件
bar.render("positionInfo.html")