• scrapy框架的使用


    1 搭建scrapy项目

    # 安装scrpay框架
    (base) C:\Users\He>conda install scrapy

    安装好之后,我们就可以通过scrapy的相关命令创建项目了。

    第1步:命令行创建项目

    切换到自己的存储目录,执行如下命令:

    # startproject命令:创建scrapy项目lianjiaSpider
    D:\PycharmProjects\python-course\python爬虫\链家租房数据教程>scrapy startproject lianjiaSpider

    第2步:创建爬虫

    切换到目录lianjiaSpider,执行如下命令:

    cd lianjiaSpider
    # genspider命令,创建scrapy爬虫lianjia.py,指定一个要爬取的域https://www.lianjia.com/city
    scrapy genspider lianjia https://www.lianjia.com/city

    第3步:通过pycharm导入项目

    导入项目,因为启动爬虫程序也在命令行操作,比较麻烦,我们在lianjiaSpider目录下新建main.py,代码如下,实现了在运行main.py即可运行该scrapy爬虫。

    #爬虫启动
    if __name__ == '__main__':
        from scrapy import cmdline
        args = "scrapy crawl lianjia".split()  
        cmdline.execute(args)

    整体目录结构如下:

    lianjiaSpider:项目名
    lianjia.py:上述第2步创建的爬虫文件: --必须
    items.py:容器对象,类似于domain对象,保存爬取到的数据 --必须
    pipelines.py:处理spaiter提取出来的items,然后持久化数据到文件或数据库 --必须
    settings.py:用户自定义相关设置
    main.py:自己添加的文件,方便运行爬虫文件
    middlewares.py:可以自定义代码,实现爬虫自动更换User-Agent,IP等功能

    2 scrapy项目实战

    本案例实现lianjia网站的几大城市的租房信息的爬取:

    需求分析:

    1:每个城市的平均租房价格(每个城市分组)

    2:租房最高的前五个小区(根据每个城市分组)

    3:合租、整租数量统计(根据每个城市分组)

    4:不同房型的出租均价、数量(根据每个城市分组)

    https://cd.lianjia.com/zufang/pg100/#contentList为例:

    分析:需要提取网页的如下6个字段: 城市名、居住类型(整租、合租)、区(比如青阳)、地址(详细小区)、房子类型(几室几厅)、价格(标价)

    我们先爬取单条数据,再扩展到爬取所有数据

    项目1:单页面数据爬取

    items.py

    import scrapy
    # LianjiaspiderItem 用于封装解析的数据
    class LianjiaspiderItem(scrapy.Item):
        # define the fields for your item here like:
        # name = scrapy.Field()
        title = scrapy.Field()
        juzhutype = scrapy.Field()
        area = scrapy.Field()
        address = scrapy.Field()
        fangzitype = scrapy.Field()
        rent = scrapy.Field()
        city = scrapy.Field()

    lianjia.py

    import scrapy
    from scrapy.http import Request
    import re
    from python爬虫.链家租房数据教程.lianjiaSpider.lianjiaSpider.items import LianjiaspiderItem
    ​
    class LianjiaSpider(scrapy.Spider):
        name = 'lianjia'
        allowed_domains = ['https://www.lianjia.com/city']  # 还有其它的域,也可以添加到这里
        start_urls = ['https://www.lianjia.com/city/'] # 爬虫的起始地址
        '''
        请求http://www.lianjia.com/city 后调用parse方法。但是这里不需要处理初始链接,直接用来拼接指定链接
        '''
        
        # 解析内容、获取下一个请求url,这里其实地址 start_urls 的内容不需要解析,因此我们直接请求想要内容的地址
        # 请求后,回调自定义函数myparse
        def parse(self,response):
            url = 'https://cd.lianjia.com/zufang/pg100/#contentList'
            yield Request(url=url,callback=self.myparse,dont_filter=True)# 解析每一个页面的数据7个字段
        def myparse(self,response):
            try:
                # 在页面中获取城市
                city = response.xpath('/html').re(';city=(.+)">')[0]
                print('城市aaaa='+city)   ## pass
    # 找到当前页的数据
                # 写(//div[@class="content__list"]/div')拿不到数据
                data_list = response.xpath('/html/body/div[3]/div[1]/div[5]/div[1]/div[1]/div')
    ​
                for data in data_list:
                    # 获取租房的介绍
                    # 只能抽取第一个item的,因此每个item前两个字段都是一样的结果
                    # title = data.xpath('//*[@id="content"]/div[1]/div[1]/div[1]/div/p[1]/a/text()').extract()
                    [0].strip()
    ​
                    title = data.xpath('./div/p[@class="content__list--item--   
                    title"]/a[@class="twoline"]/text()').extract()[0].strip()
                    print('标题aaaa=' + title)
    ​
                    # 获取 租房类型 整租或者合租
                    # 这里使用正则,提取开始的两个字。在介绍,头两个就是租房的类型
                    juzhutype = re.findall('^(.+)·', title)[0]
                    # 获取地区区域
                    area = data.xpath('./div/p[@class="content__list--item--des"]/a/text()').extract()[0]
                    # 具体地址
                    address = '-'.join(data.xpath('./div/p[@class="content__list--item--des"]/a/text()').extract())
                    # 房子的类型 比如一室一厅
                    fangzitype = data.xpath('./div/p[@class="content__list--item--des"]/text()').extract()[-2].strip()
                    # 租金
                    rent = data.xpath('./div/span/em/text()').extract()[0]
    ​
                    # 封装成LianjiaItem容器,交给管道处理
                    yield LianjiaspiderItem(title=title, juzhutype=juzhutype, area=area, address=address,     
                    fangzitype=fangzitype,rent=rent, city=city)except:
                print('错误数据')
                # 如果有错误数据,就写1 在cuowu.text文件中 统计数量
                with open('cuowu.txt', 'a', encoding='utf8') as f:
                    f.write('1\n')

    pipelines.py

    import pandas as pd
    from sqlalchemy import create_engine
    ​
    class LianjiaspiderPipeline(object):
        # 创建链接mysql数据库的引擎
        def __init__(self):
            self.engine = create_engine('mysql+pymysql://root:admin@127.0.0.1:3306/pythondb?charset=utf8')
    ​
    # 每次接收到一个item,然后对这个item进行处理
        def process_item(self, item, spider):
            # 存储方法1、从item中取出数据,拼接成字符串,写入lianjia.txt  
            # --测试通过,会在项目根目录生成lianjia.txt 并写入数据
            # title = item['title']
            # juzhutype = item['juzhutype']
            # area = item['area']
            # address = item['address']
            # fangzitype = item['fangzitype']
            # rent = item['rent']
            # city = item['city']
            # print("++++++++++++++++++++++++++++++++++++=")
            # with open('lianjia.txt', 'a', encoding='utf8') as f:
            #     mystr = '{}|{}|{}|{}|{}|{}|{}\n'.format(title, juzhutype, area, address, fangzitype, rent, city)
            #     f.write(mystr)
    # 存储方法2、存入mysql数据库
            cc =dict(item)
            data = pd.DataFrame(dict(item),index=[0])
            data.to_sql('lianjia',self.engine,if_exists='append',index=False)
    ​
            return item

    settings.py

    BOT_NAME = 'lianjiaSpider'
    SPIDER_MODULES = ['lianjiaSpider.spiders']
    NEWSPIDER_MODULE = 'lianjiaSpider.spiders'
    #设置自己的管道LianjiaspiderPipeline及顺序
    ITEM_PIPELINES = {
        'lianjiaSpider.pipelines.LianjiaspiderPipeline':300,
    }
    ROBOTSTXT_OBEY = False  #不遵守网站规则
    RETRY_ENABLED = False   #禁止重试
    DOWNLOAD_TIMEOUT = 30   #设置下载超时
    COOKIES_ENABLED = False #禁止cookeys
    DOWMLOAD_DELY= 3        #设置下载延迟
    REACTOR_THREADPOOL_MAXSIZE = 10 #设置线程池

    middlewares.py:该项目中,该文件使用默认即可。

    运行爬虫

    根据pipelines中持久化数据方式的不同

    方法1:持久化到txt文件中:

      方法2:持久化数据到mysql中 首先需要创建数据库pythondb,然后创建表lianjia

    # sql代码
    create table lianjia(
      title VARCHAR(255),
      juzhutype VARCHAR(255),
      area VARCHAR(255),
      address VARCHAR(255),
      fangzitype VARCHAR(255),
      rent INT(11),
      city VARCHAR(100)
    );

     到此,用scrapy框架爬取链家租房数据的“单个页面”就调通了。

    项目2:所有数据爬取

    数据分析,往往需要爬取很多页面的数据,在大量的数据背后,找出规律。因此,我们把爬取单个页面的代码,修改成爬取所有租房数据的代码,其实也很简单,在“单页面数据爬取”项目中,修改一下lianjia.py文件即可实现。

    lianjia.py

    仅仅修改了parse函数,把原来的单个url,换成了拼接成的500个url。

    import scrapy
    from scrapy.http import Request
    import re
    from python爬虫.链家租房数据教程.lianjiaSpider.lianjiaSpider.items import LianjiaspiderItem
    ​
    class LianjiaSpider(scrapy.Spider):
        name = 'lianjia'
        allowed_domains = ['https://www.lianjia.com/city']  # 还有其它的域,也可以添加到这里
        start_urls = ['https://www.lianjia.com/city/'] # 爬虫的起始地址
        '''
        请求http://www.lianjia.com/city 后调用parse方法。但是这里不需要处理初始链接,直接用来拼接指定链接。
        parse方法用于解析内容、获取下一个请求url,这里其实地址 start_urls 的内容不需要解析,因此我们直接请求想要内容的地址
        请求后,回调自定义函数myparse
        '''
        def parse(self,response):
            # 成都、重庆、背景、上海、贵阳五个城市的域名
            city_urls = ['https://cd.lianjia.com/', 'https://cq.lianjia.com/','https://bj.lianjia.com/', 
            'https://sh.lianjia.com/', 'https://gy.lianjia.com/']
            print(response)
            # url是城市链接
            # page是页数,这几个城市最大页数都是100
            for url in city_urls:
                for page in range(1,101):
                    # 拼接出类似 https://bj.lianjia.com/zufang/pg1/#contentList 的链接
                    # 每个城市100页,5个城市,大概能拼接出500个租房信息页面的url
                    myurl = url+'zufang/pg%s/#contentList' % page
                    '''
                    把url给引擎,让它去调度并下载。
                    callback 是指定下载完成后调用的方法,这里是myparse
                    dont_filter 禁止scrapy自带的url过滤,这里我们不需要过滤,因为这里的url是不会重复的
                    '''
                    yield Request(url=url, callback=self.myparse, dont_filter=True)
            # print("进来了吗")  #  pass
            # print(response)
            # url = 'https://cd.lianjia.com/zufang/pg100/#contentList'
            # yield Request(url=url,callback=self.myparse,dont_filter=True)
    ​
        # # 解析每一个页面的数据
        def myparse(self,response):
            try:
                # 在页面中获取城市
                city = response.xpath('/html').re(';city=(.+)">')[0]
                print('城市aaaa='+city)   ## pass
    ​
                # 找到当前页的数据
                # 写(//div[@class="content__list"]/div')拿不到数据
                data_list = response.xpath('/html/body/div[3]/div[1]/div[5]/div[1]/div[1]/div')
    ​
                for data in data_list:
                    # 获取租房的介绍
                    # 只能抽取第一个item的,因此每个item前两个字段都是一样的结果
                    # title = data.xpath('//*[@id="content"]/div[1]/div[1]/div[1]/div/p[1]/a/text()').extract()
                    [0].strip()
    ​
                    title = data.xpath('./div/p[@class="content__list--item--
                    title"]/a[@class="twoline"]/text()').extract()[0].strip()
                    print('标题aaaa=' + title)
    ​
                    # 获取 租房类型 整租或者合租
                    # 这里使用正则,提取开始的两个字。在介绍,头两个就是租房的类型
                    juzhutype = re.findall('^(.+)·', title)[0]
                    # 获取地区区域
                    area = data.xpath('./div/p[@class="content__list--item--des"]/a/text()').extract()[0]
                    # 具体地址
                    address = '-'.join(data.xpath('./div/p[@class="content__list--item--des"]/a/text()').extract())
                    # 房子的类型 比如一室一厅
                    fangzitype = data.xpath('./div/p[@class="content__list--item--des"]/text()').extract()[-2].strip()
                    # 租金
                    rent = data.xpath('./div/span/em/text()').extract()[0]
                    # 封装成LianjiaItem容器,交给管道处理
                    yield LianjiaspiderItem(title=title, juzhutype=juzhutype, area=area, address=address, 
                    fangzitype=fangzitype,rent=rent, city=city)
            except:
                print('错误数据')
                # 如果有错误数据,就写1 在cuowu.text文件中 统计数量
                with open('cuowu.txt', 'a', encoding='utf8') as f:
                    f.write('1\n')

    运行爬虫

    根据pipelines中持久化数据方式的不同

    方法1:持久化到txt文件中:

     方法2:持久化数据到mysql中 首先需要创建数据库pythondb,然后创建表lianjia 

      一共12843条数据存储mysql,存入文件是13769条。 为什么记录不一样: 可能是两个原因: 1:本身网页数据在变动,存在个别差异是正常现象; 2:在爬取和存储数据到mysql的过程中,电脑没电了,重新开机后爬虫继续爬取,导致中间部分数据丢失。到此,scrapy爬取数据项目就结束了。

    展望:

    为了防止爬虫被网站的反爬规则识别,可以通过如下方式加强爬虫:

    1、配置动态User-Agent --可在middlewares.py中配置

    2、使用ip地址池 --可在middlewares.py中配置

    3、禁用cookie --可在settings.py中配置

    4、设置延迟下载 --可在settings.py中配置

    5、使用Crawlera,使得项目中的所有request都通过Crawlera发出

    备注

    调试代码

    1、在需要查看的参数左边,对应的行号前点击一下,出现一个红色的原点

    2、然后打开main.py,右键--》debug main即可以debug模式运行代码

    3、单机左下角的step into mycode,便可以挨行运行代码

    报错

    1、raise ValueError("If using all scalar values, you must pass an index")

          出错语句: data = pd.DataFrame(dict(item)

          解决: data = pd.DataFrame(dict(item),index=[0]) # 加上index=[0]即可

     

     

     

     

     

  • 相关阅读:
    1.数据结构《Pytorch神经网络高效入门教程》Deeplizard
    plt.figure()的使用,plt.plot(),plt.subplot(),plt.subplots()和图中图
    可变长参数
    np.linspace,numpy中的linspace()
    python和numpy中sum()函数的异同
    maven install 错误
    spring boot启动后执行方法
    java 定时任务多线程处理
    java 生成txt文件
    java 方法超时
  • 原文地址:https://www.cnblogs.com/hemomo/p/15726857.html
Copyright © 2020-2023  润新知