草庐IT

用Scrapy和Selenium爬取动态数据

爱吃雪糕的小布丁 2023-04-05 原文

文章参考千锋教育大佬的课程:
https://www.bilibili.com/video/BV1QY411F7Vt?p=1&vd_source=5f425e0074a7f92921f53ab87712357b
,多谢大佬的课程

一、 用Selenium操作谷歌浏览器,登录TB账号获取Cookie

  因为TB网的搜索功能需要登录之后才能使用,所以我们要通过程序去控制浏览器实现登录功能,然后再获取登录之后的Cookie.
  首先创建一个Chrome浏览器对象,用这个对象去操控谷歌浏览器:

import json
from selenium import webdriver

def create_chrome_driver(*, headless=False):  # 创建谷歌浏览器对象,用selenium控制浏览器访问url
    options = webdriver.ChromeOptions()
    if headless:  # 如果为True,则爬取时不显示浏览器窗口
        options.add_argument('--headless')

    # 做一些控制上的优化
    options.add_experimental_option('excludeSwitches', ['enable-automation'])
    options.add_experimental_option('useAutomationExtension', False)
    # 创建浏览器对象
    browser = webdriver.Chrome(options=options,executable_path=r"D:\python爬虫学习\Scrapy框架学习\TaoSpider\venv\Lib\site-packages\chromedriver.exe")
    # 破解反爬措施
    browser.execute_cdp_cmd(
        'Page.addScriptToEvaluateOnNewDocument',
        {'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})'}
    )

    return browser

  接着就可以通过这个对象去操作浏览器登录TB网,并且把Cookie存进taobao2.json文件中:

# 模拟登录
import json
import time

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from utils import create_chrome_driver

browser = create_chrome_driver()
browser.get('https://login.taobao.com')

# 隐式等待
browser.implicitly_wait(10)

# 获取页面元素模拟用户输入和点击行为
username_input = browser.find_element(By.CSS_SELECTOR, '#fm-login-id')
username_input.send_keys('xxx')  # 填写用户名

password_input = browser.find_element(By.CSS_SELECTOR, '#fm-login-password')
password_input.send_keys('xxx')  # 填写对应的密码

# 登录按钮
login_button = browser.find_element(By.CSS_SELECTOR, '#login-form > div.fm-btn > button')
login_button.click()

# 显示等待
# wait_obj = WebDriverWait(browser, 10)
# wait_obj.until(expected_conditions.presence_of_element_located((By.CSS_SELECTOR, 'div.m-userinfo')))
time.sleep(15)

# 获取登录的cookie数据,并且写入文件
with open('taobao2.json', 'w') as file:
    json.dump(browser.get_cookies(), file)

  最后我们在进行发送请求的时候,把Cookie加进请求当中,就可以访问TB网进行搜索商品了。将Cookie加进浏览器对象中:

def add_cookies(browser, cookie_file):  # 给浏览器对象添加登录的cookie
    with open(cookie_file, 'r') as file:
        cookie_list = json.load(file)
        for cookie_dict in cookie_list:
            if cookie_dict['secure']:
                browser.add_cookie(cookie_dict)

  我们可以先来测试一下是否能操作浏览器,在进行爬取之前得先获取登录的Cookie,所以先执行登录的代码,第一小节的代码在普通python文件中就能执行,可以不用在Scrapy项目中执行。接着执行访问搜索页面的代码,代码为:

'''
通过搜索获取商品信息
'''

from utils import create_chrome_driver, add_cookies

browser = create_chrome_driver()  # 创建谷歌浏览器对象,通过控制浏览器来访问url
browser.get('https://www.taobao.com')
add_cookies(browser, 'taobao2.json')
browser.get('https://s.taobao.com/search?q=手机&s=0')  # 淘宝上的搜索功能必须要登录才能搜索,需要用cookie来亮明身份

  程序会自动操控浏览器去访问TB搜索页:

二、蜘蛛程序的编写

  我们这里爬取手机、笔记本电脑、键鼠套装的数据,每一个类被爬取两页,一页有48条数据,一共就是288条数据,每一页都会有几条时广告,所以爬取的数据是少于288条的。蜘蛛程序代码如下:

import scrapy
from scrapy import Request,Selector

from TaoSpider.items import TaospiderItem


class TaobaoSpider(scrapy.Spider):
    name = 'taobao'
    allowed_domains = ['taobao.com']

    def start_requests(self):
        keywords = ['手机', '笔记本电脑', '键鼠套装']
        for keyword in keywords:
            for page in range(2):
                url = f'https://s.taobao.com/search?q={keyword}&s={48 * page}'
                yield Request(url=url)

    # def parse_detail(self, response, **kwargs):
    #     pass

    def parse(self, response, **kwargs):  # 淘宝的数据是通过js动态渲染出来的,不是静态内容,通过选择器拿不到,我们要通过selenium帮助我们拿到,在数据管道中实现
        sel = Selector(response)
        selectors = sel.css('div.items > div.item.J_MouserOnverReq > div.ctx-box.J_MouseEneterLeave.J_IconMoreNew')
        for selector in selectors:  # type: Selector
            item = TaospiderItem()
            item['title'] = ''.join(selector.css('div.row.row-2.title > a::text').extract()).strip()
            item['price'] = selector.css('div.row.row-1.g-clearfix > div.price.g_price.g_price-highlight > strong::text').extract_first().strip()
            item['deal_count'] = selector.css('div.row.row-1.g-clearfix > div.deal-cnt::text').extract_first().strip()
            item['shop'] = selector.css('div.row.row-3.g-clearfix > div.shop > a > span:nth-child(2)::text').extract_first().strip()
            item['location'] = selector.css('div.row.row-3.g-clearfix > div.location::text').extract_first().strip()
            yield item

  数据条目Items的代码如下:

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class TaospiderItem(scrapy.Item):
    title = scrapy.Field()  # 标题
    price = scrapy.Field()  # 价格
    deal_count = scrapy.Field()  # 销量
    shop = scrapy.Field()  # 店铺名称
    location = scrapy.Field()  # 店铺地址

三、中间件的编写

  我这里主要是重新编写下载中间件,因为Taobao的数据是用js动态渲染的,所以我们不能用Scrapy默认的下载器抓取数据,默认的下载器只能抓取静态数据。想要抓取动态数据的话,需要用到
Selenium。并且Taobao需要登录之后才能使用搜索功能,所以我们把第一小节的代码一起用上,下载中间件代码为:

class TaospiderDownloaderMiddleware:
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.

    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        # s = cls()
        # crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        # return s
        return cls()

    def __init__(self):  # 初始化数据管道时模拟用户登录
        self.browser = create_chrome_driver()
        self.browser.get('https://www.taobao.com')
        add_cookies(self.browser, 'taobao2.json')

    def __del__(self):  # 销毁时执行该方法
        self.browser.close()

    def process_request(self, request: Request, spider):  # 不用原来的下载器去下载,自己编写一个selenium下载器
        # Called for each request that goes through the downloader
        # middleware.

        # Must either:
        # - return None: continue processing this request
        # - or return a Response object
        # - or return a Request object
        # - or raise IgnoreRequest: process_exception() methods of
        #   installed downloader middleware will be called
        self.browser.get(request.url)
        # page_source 是带了动态内容的网页源代码,和直接在浏览器看到的源代码不一样,直接在浏览器看到的只有静态内容
        # 通过浏览器请求之后,直接返回响应回来的内容通过引擎传递给解析器
        return HtmlResponse(url=request.url, body=self.browser.page_source,
                            request=request, encoding='utf-8')

    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        # Must either;
        # - return a Response object
        # - return a Request object
        # - or raise IgnoreRequest
        return response

    def process_exception(self, request, exception, spider):
        # Called when a download handler or a process_request()
        # (from other downloader middleware) raises an exception.

        # Must either:
        # - return None: continue processing this exception
        # - return a Response object: stops process_exception() chain
        # - return a Request object: stops process_exception() chain
        pass

    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)

  别忘了在配置文件中开启中间件:

DOWNLOADER_MIDDLEWARES = {
   'TaoSpider.middlewares.TaospiderDownloaderMiddleware': 543,
}

四、数据存储——数据管道的编写

  如果不想把数据存进第三方的,比如数据库和excel的话,就不用看这里了直接在命令行执行:scrapy crawl taobao -o taobao.csv,就能把数据存进csv文件中了。我这里在数据管道中把数据存进Excel。数据管道代码:

class TaospiderPipeline:

    def __init__(self):
        self.wb = openpyxl.Workbook()  # 创建工作簿
        self.ws = self.wb.active  # 拿到默认激活的工作表
        self.ws.title = 'TaoBaoData'  # 工作表名称
        self.ws.append(('标题','价格','销量','店铺名称','店铺地址'))  # 表头

    def close_spider(self, spider):  # 爬虫停止运行的时候执行该方法,钩子函数,自己执行不需要调用
        self.wb.save('淘宝商品数据.xlsx')


    def process_item(self, item, spider):
        title = item.get('title', '')  # 如果字典中的title值为空的话,就把''(空值)赋给title变量,写法一
        price = item.get('price') or 0  # 如果字典中的title值为空的话,就把''(空值)赋给title变量,写法二
        deal_count = item.get('deal_count', '')
        shop = item.get('shop', '')
        location = item.get('location', '')
        self.ws.append((title, price, deal_count, shop, location))  #
        return item

  别忘了在配置文件中开启数据管道:

ITEM_PIPELINES = {
   'TaoSpider.pipelines.TaospiderPipeline': 300,
}

  最后在命令行中执行:scrapy crawl taobao,taobao为蜘蛛程序的名字。

  Taobao会有滑动验证码反爬措施,这里只能爬取几次,之后就会被封掉。一般解封时间为一个小时,如果想绕过这个滑动验证码,需要改Chrome浏览器的动态程序,请读者自行上网百度。

有关用Scrapy和Selenium爬取动态数据的更多相关文章

  1. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  2. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  3. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  4. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  5. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  6. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  7. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  8. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

  9. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  10. SPI接收数据异常问题总结 - 2

    SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手

随机推荐