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]即可

 

 

 

 

 

posted @ 2021-12-24 12:06  geiliHe  阅读(339)  评论(0编辑  收藏  举报