Scrapy+Selenium爬取动态渲染网站

一、概述

使用情景

在通过scrapy框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载的情况发生,如果直接使用scrapy对其url发请求,是绝对获取不到那部分动态加载出来的数据值。但是通过观察我们会发现,通过浏览器进行url请求发送则会加载出对应的动态加载出的数据。那么如果我们想要在scrapy也获取动态加载出的数据,则必须使用selenium创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值

使用流程

1. 重写爬虫文件的__init__()构造方法,在该方法中使用selenium实例化一个浏览器对象

2. 重写爬虫文件的closed(self,spider)方法,在其内部关闭浏览器对象,该方法是在爬虫结束时被调用.

3. 在settings配置文件中开启下载中间件

 

二、案例演示

这里以房天下为例,爬取楼盘信息,链接如下:

https://sh.newhouse.fang.com/house/s/a75-b91/?ctm=1.sh.xf_search.page.1

 

页面分析

获取信息列表

//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]

它会获取20条信息

 

获取名称

//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]//div[@class="nlcd_name"]/a/text()

结果如下:

 

获取价格

//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]//div[@class="nhouse_price"]/span/text()

结果如下:

 

注意:别看它只有18条,因为还有2条,价格没有公布,所以获取不到。因此,后续我会它一个默认值:价格待定

 

获取区域

//*[@id="newhouse_loupai_list"]/ul/li//div[@class="relative_message clearfix"]//a/span/text()

结果如下:

 

 注意:别看它只有17条,因为还有3条,不在国内。比如泰国,老挝等。因此,后续我会它一个默认值:国外

 

获取地址

//*[@id="newhouse_loupai_list"]/ul/li//div[@class="relative_message clearfix"]/div/a/text()

结果如下:

 

注意:多了17条,为什么呢?因此地址有些含有大段的空行,有些地址还包含了区域信息。因此,后续我会做一下处理,去除多余的换行符,通过正则匹配出地址信息。

 

获取状态

//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]//span[@class="inSale"]/text()

 

结果如下:

 

注意:少了4条,那是因为它的状态是待售。因此,后续我会做一下处理,没有匹配的,给定默认值。

 

项目代码

通过以上页面分析出我们要的结果只会,就可以正式编写代码了。

创建项目

打开Pycharm,并打开Terminal,执行以下命令

scrapy startproject fang
cd fang
scrapy genspider newhouse sh.newhouse.fang.com

 

在scrapy.cfg同级目录,创建bin.py,用于启动Scrapy项目,内容如下:

# !/usr/bin/python3
# -*- coding: utf-8 -*-
#在项目根目录下新建:bin.py
from scrapy.cmdline import execute
# 第三个参数是:爬虫程序名
execute(['scrapy', 'crawl', 'newhouse',"--nolog"])

 

创建好的项目树形目录如下:

./
├── bin.py
├── fang
│   ├── __init__.py
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       └── newhouse.py
└── scrapy.cfg

 

修改newhouse.py

# -*- 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)
View Code

 

修改items.py

# -*- 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()
View Code

 

修改pipelines.py

# -*- 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()
View Code

注意:这里为了方便,保存在一个json文件中。当然,也可以设置保存到数据库中。

 

修改settings.py,应用pipelines

ITEM_PIPELINES = {
   'fang.pipelines.FangPipeline': 300,
}

 

执行bin.py,启动爬虫项目,效果如下:

 

 

查看文件fang_pipline.json,内容如下:

 

注意:本次访问的页面,只有6页,每页20条结果。因此可以获取到120条信息。

 

本文参考链接:

https://www.cnblogs.com/bk9527/p/10504883.html

 

posted @ 2020-09-17 14:56  肖祥  阅读(1085)  评论(0编辑  收藏  举报