前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Scrapy+Selenium爬取动态渲染网站

Scrapy+Selenium爬取动态渲染网站

作者头像
py3study
发布2020-11-05 10:22:09
1.5K0
发布2020-11-05 10:22:09
举报
文章被收录于专栏:python3python3

一、概述

使用情景

在通过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条信息

1.png
1.png

获取名称

代码语言:javascript
复制
//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]//div[@class="nlcd_name"]/a/text()

结果如下:

1.png
1.png

获取价格

代码语言:javascript
复制
//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]//div[@class="nhouse_price"]/span/text()

结果如下:

1.png
1.png

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

获取区域

代码语言:javascript
复制
//*[@id="newhouse_loupai_list"]/ul/li//div[@class="relative_message clearfix"]//a/span/text()

结果如下:

1.png
1.png

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

获取地址

代码语言:javascript
复制
//*[@id="newhouse_loupai_list"]/ul/li//div[@class="relative_message clearfix"]/div/a/text()

结果如下:

1.png
1.png

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

获取状态

代码语言:javascript
复制
//*[@id="newhouse_loupai_list"]/ul/li//div[@class="nlc_details"]//span[@class="inSale"]/text()

结果如下:

1.png
1.png

注意:少了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,启动爬虫项目,效果如下:

1.png
1.png

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

1.png
1.png

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

本文参考链接:

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-11-04 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
    • 使用情景
      • 使用流程
      • 二、案例演示
        • 页面分析
          • 获取信息列表
          • 获取名称
          • 获取价格
          • 获取区域
          • 获取地址
        • 获取状态
          • 项目代码
            • 创建项目
        相关产品与服务
        消息队列 TDMQ
        消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档