一、概述
使用情景
在通过scrapy框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载的情况发生,如果直接使用scrapy对其url发请求,是绝对获取不到那部分动态加载出来的数据值。但是通过观察我们会发现,通过浏览器进行url请求发送则会加载出对应的动态加载出的数据。那么如果我们想要在scrapy也获取动态加载出的数据,则必须使用selenium创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值
使用流程
1. 重写爬虫文件的__init__()构造方法,在该方法中使用selenium实例化一个浏览器对象
2. 重写爬虫文件的closed(self,spider)方法,在其内部关闭浏览器对象,该方法是在爬虫结束时被调用.
3. 在settings配置文件中开启下载中间件
二、案例演示
这里以房天下为例,爬取楼盘信息,链接如下:
代码语言:javascript复制https://sh.newhouse.fang.com/house/s/a75-b91/?ctm=1.sh.xf_search.page.1
页面分析
获取信息列表
代码语言:javascript复制//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]
它会获取20条信息
获取名称
代码语言:javascript复制//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]//div[@class="nlcd_name"]/a/text()
结果如下:
获取价格
代码语言:javascript复制//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]//div[@class="nhouse_price"]/span/text()
结果如下:
注意:别看它只有18条,因为还有2条,价格没有公布,所以获取不到。因此,后续我会它一个默认值:价格待定
获取区域
代码语言:javascript复制//*[@id="newhouse_loupai_list"]/ul/li//div[@class="relative_message clearfix"]//a/span/text()
结果如下:
注意:别看它只有17条,因为还有3条,不在国内。比如泰国,老挝等。因此,后续我会它一个默认值:国外
获取地址
代码语言:javascript复制//*[@id="newhouse_loupai_list"]/ul/li//div[@class="relative_message clearfix"]/div/a/text()
结果如下:
注意:多了17条,为什么呢?因此地址有些含有大段的空行,有些地址还包含了区域信息。因此,后续我会做一下处理,去除多余的换行符,通过正则匹配出地址信息。
获取状态
代码语言:javascript复制//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]//span[@class="inSale"]/text()
结果如下:
注意:少了4条,那是因为它的状态是待售。因此,后续我会做一下处理,没有匹配的,给定默认值。
项目代码
通过以上页面分析出我们要的结果只会,就可以正式编写代码了。
创建项目
打开Pycharm,并打开Terminal,执行以下命令
代码语言:javascript复制scrapy startproject fang
cd fang
scrapy genspider newhouse sh.newhouse.fang.com
在scrapy.cfg同级目录,创建bin.py,用于启动Scrapy项目,内容如下:
代码语言:javascript复制# !/usr/bin/python3
# -*- coding: utf-8 -*-
#在项目根目录下新建:bin.py
from scrapy.cmdline import execute
# 第三个参数是:爬虫程序名
execute(['scrapy', 'crawl', 'newhouse',"--nolog"])
创建好的项目树形目录如下:
代码语言:javascript复制./
├── bin.py
├── fang
│ ├── __init__.py
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders
│ ├── __init__.py
│ └── newhouse.py
└── scrapy.cfg
修改newhouse.py
代码语言:javascript复制# -*- coding: utf-8 -*-
import scrapy
from scrapy import Request # 导入模块
import math
import re
from fang.items import FangItem
from selenium.webdriver import ChromeOptions
from selenium.webdriver import Chrome
class NewhouseSpider(scrapy.Spider):
name = 'newhouse'
allowed_domains = ['sh.newhouse.fang.com']
base_url = "https://sh.newhouse.fang.com/house/s/a75-b91/?ctm=1.sh.xf_search.page."
# start_urls = [base_url str(1)]
# 实例化一个浏览器对象
def __init__(self):
# 防止网站识别Selenium代码
options = ChromeOptions()
options.add_argument("--headless") # => 为Chrome配置无头模式
options.add_experimental_option('excludeSwitches', ['enable-automation'])
options.add_experimental_option('useAutomationExtension', False)
self.browser = Chrome(options=options)
self.browser.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
"source": """
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
})
"""
})
super().__init__()
def start_requests(self):
print("开始爬虫")
self.base_url = "https://sh.newhouse.fang.com/house/s/a75-b91/?ctm=1.sh.xf_search.page."
url = self.base_url str(1)
print("url",url)
# url = "https://news.163.com/"
response = scrapy.Request(url, callback=self.parse_index)
yield response
# 整个爬虫结束后关闭浏览器
def close(self, spider):
print("关闭爬虫")
self.browser.quit()
# 访问主页的url, 拿到对应板块的response
def parse_index(self, response):
print("访问主页")
# 获取分页
# 查询条数
ret_num = response.xpath('//*[@id="sjina_C01_47"]/ul/li[1]/b/text()').extract_first()
# print("ret_num", ret_num, type(ret_num))
# 计算分页,每一页20条
jsfy = int(ret_num) / 20
# 向上取整
page_num = math.ceil(jsfy)
# print("page_num",page_num)
for n in range(1, page_num):
n = 1
# 下一页url
url = self.base_url str(n)
print("url", url)
# 访问下一页,有返回时,调用self.parse_details方法
yield scrapy.Request(url=url, callback=self.parse_details)
def parse_details(self, response):
# 获取页面中要抓取的信息在网页中位置节点
node_list = response.xpath('//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]')
count = 0
# 遍历节点,进入详情页,获取其他信息
for node in node_list:
count = 1
try:
# # 名称
nlcd_name = node.xpath('.//div[@class="nlcd_name"]/a/text()').extract()
if nlcd_name:
nlcd_name = nlcd_name[0].strip()
print("nlcd_name", nlcd_name)
# # # 价格
price = node.xpath('.//div[@class="nhouse_price"]/span/text()').extract()
# print("原price",price,type(price))
if price:
price = price[0].strip()
if not price:
price = "价格待定"
print("price", price)
# 区域
region_ret = node.xpath('.//div[@class="relative_message clearfix"]//a/span/text()').extract()
region = ""
if region_ret:
# if len(region) >=2:
region_ret = region_ret[0].strip()
# 正则匹配中括号的内容
p1 = re.compile(r'[[](.*?)[]]', re.S)
region = re.findall(p1, region_ret)
if region:
region = region[0]
# print("region",region)
# # # # 地址
address_str = node.xpath('.//div[@class="relative_message clearfix"]/div/a/text()').extract()
address = ""
# 判断匹配结果,截取地址信息
if address_str:
if len(address_str) >= 2:
address_str = address_str[1].strip()
else:
address_str = address_str[0].strip()
# print("address_str", address_str)
# 判断地址中,是否含有区域信息,比如[松江]
p1 = re.compile(r'[[](.*?)[]]', re.S) # 最小匹配
address_ret = re.findall(p1, address_str)
if address_ret:
# 截图地区
region = address_ret[0]
# 地址拆分
add_cut_str = address_str.split()
# 截取地址
if add_cut_str:
address = add_cut_str[1]
else:
address = address_str
# 为空时,表示在国外
if not region_ret:
region = "国外"
print("region", region)
print("address", address)
# # # 状态
status = node.xpath('.//span[@class="inSale"]/text()').extract_first()
# status = node.xpath('.//div[@class="fangyuan pr"]/span/text()').extract_first()
if not status:
status = "待售"
print("status", status)
# item
item = FangItem()
item['nlcd_name'] = nlcd_name
item['price'] = price
item['region'] = region
item['address'] = address
item['status'] = status
yield item
except Exception as e:
print(e)
print("本次爬取数据: %s条" % count)
修改items.py
代码语言:javascript复制# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html
import scrapy
class FangItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
nlcd_name = scrapy.Field()
price = scrapy.Field()
region = scrapy.Field()
address = scrapy.Field()
status = scrapy.Field()
修改pipelines.py
代码语言:javascript复制# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
import json
class FangPipeline(object):
def __init__(self):
# python3保存文件 必须需要'wb' 保存为json格式
self.f = open("fang_pipline.json", 'wb')
def process_item(self, item, spider):
# 读取item中的数据 并换行处理
content = json.dumps(dict(item), ensure_ascii=False) ',n'
self.f.write(content.encode('utf=8'))
return item
def close_spider(self, spider):
# 关闭文件
self.f.close()
注意:这里为了方便,保存在一个json文件中。当然,也可以设置保存到数据库中。
修改settings.py,应用pipelines
代码语言:javascript复制ITEM_PIPELINES = {
'fang.pipelines.FangPipeline': 300,
}
执行bin.py,启动爬虫项目,效果如下:
查看文件fang_pipline.json,内容如下:
注意:本次访问的页面,只有6页,每页20条结果。因此可以获取到120条信息。
本文参考链接:
https://www.cnblogs.com/bk9527/p/10504883.html