Scrapy爬取顶点小说网

Scrapy爬取小说

爬取目标:顶点小说网

1、Scrapy的安装

pip install scrapy

2、Scrapy的介绍

  1. 创建项目
scrapy startproject xxx     xxx项目名字

WX20180623-141339@2x-w341

项目结构
  1. items.py 负责数据模型的建立,类似实体类。
  2. middlewares.py 自己定义的中间件
  3. pipelines.py 负责对spider返回数据的处理
  4. settings.py 复制对整个爬虫的配置
  5. spiders 目录负责存放继承自scrapy的爬虫类
  6. scrapy.cfg scrapy基础配置

Scrapy默认是不能在IDE中调试的,我们在根目录中新建一个py文件叫:entrypoint.py;在里面写入以下内容:

from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'scrapy_demo'])

注意!第二行中代码中的前两个参数是不变的,第三个参数请使用自己的spider的名字。
scrapy_architecture-w350

模块作用介绍
  1. Scrapy Engine: 这是引擎,负责Spiders、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等等。
  2. Scheduler(调度器): 它负责接受引擎发送过来的requests请求,并按照一定的方式进行整理排列,入队、并等待Scrapy Engine(引擎)来请求时,交给引擎。
  3. Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spiders来处理。
  4. Spiders:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)。
  5. Item Pipeline:它负责处理Spiders中获取到的Item,并进行处理,比如去重,持久化存储(存数据库,写入文件,总之就是保存数据用的)。
  6. Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
  7. Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spiders中间‘通信‘的功能组件(比如进入Spiders的Responses;和从Spiders出去的Requests)
创建项目之后
  1. 第一件事情:是在items.py文件中定义一些字段,这些字段用来临时存储你需要保存的数据。方便后面保存数据到其他地方,比如数据库 或者 本地文本之类的。
  2. 第二件事情在spiders文件夹中编写自己的爬虫
  3. 第三件事情在pipelines.py中存储自己的数据
  4. 还有一件事情,不是非做不可的,就settings.py文件 并不是一定要编辑的,只有有需要的时候才会编辑。
  5. 建议一点:在大家调试的时候建议大家在settings.py中取消下面几行的注释:
    WX20180624-005355@2x-w749
    这几行注释的作用是,Scrapy会缓存你有的Requests!当你再次请求时,如果存在缓存文档则返回缓存文档,而不是去网站请求,这样既加快了本地调试速度,也减轻了网站的压力。

3、Scrapy的使用

第一步:在items.py定义爬取的字段
import scrapy

class ScrapyDemoItem(scrapy.Item):
    # define the fields for your item here like:
    # 小说名字
    name = scrapy.Field()
    # 小说作者
    author = scrapy.Field()
    # 文章类别
    category = scrapy.Field()

Item对象是种简单的容器,保存爬取到的数据。提供了类似词典的API及简单的语法

第二步:在Spider中编写爬虫

在spiders文件中新建一个scrapy_demo.py文件
倒入需要用的模块

import re
import scrapy
from bs4 import BeautifulSoup
from scrapy.http import Request     # 一个单独的Request模块,需要跟进url的时候用它
from scrapy_demo.items import ScrapyDemoItem    #导入在items中定义的字段
遇到的问题:
在倒入ScrapyDemoItem类的时候遇到了,倒入失败的情况。
解决方法:
目录的某个包中的某个py文件要调用另一个py文件中的函数,首先要将目录     设置为source root,这样才能从包中至上至上正确引入函数。
步骤:目录 > 右键 > make directory as > source root
分析爬取的地址url
分析地址:http://www.23us.so/list/1_1.html
格式为:http://www.23us.so/list/x_y.html
其中x表示小说的分类,y代表页码。
class MySpider(scrapy.Spider):
    name = 'scrapy_demo'
    allowed_domains = ['www.23us.so']
    bash_url = 'http://www.23us.so/list/'
    bashurl = '.html'

    """爬取的文章类型"""
    def start_requests(self):
        for i in range(1,2):
            url = self.bash_url + str(i) + '_1' + self.bashurl
            yield Request(url, self.parse)
    def parse(self, response):
        print(response.text)
  1. name就是我们在entrypoint.py文件中的第三个参数。
  2. 定义了一个allowed_domains;这个不是必须的;但是在某写情况下需要用得到,比如使用爬取规则的时候就需要了;它的作用是只会跟进存在于allowed_domains中的URL。不存在的URL会被忽略。
  3. 注意最后一行调用的不是requests,而是导入的Request包,来跟进我们的URL(并将返回的response作为参数传递给self.parse, 嗯!这个叫回调函数!)
可以测试一下是不是爬取每个小说的页面,scrapy使用的异步。因为Scrapy遵循了robots规则,如果你想要获取的页面在robots中被禁止了,Scrapy是会忽略掉的
获取每个小说分类的最后一页
    def parse(self, response):
        soup = BeautifulSoup(response.text, 'lxml')
        # max_num = soup.find(id = 'pagelink').find_all(name='a')[-1].text
        max_num = 4
        bashurl = str(response.url)[:-7]
        for num in range(1, int(max_num) + 1):  # 拼接完整的url
            url = bashurl + '_' + str(num) + self.bashurl
            yield Request(url, callback=self.get_name)
  1. 参数response是start_requests中最后使用生成器返回Request()将返回的response作为parse的参数。
  2. 最后一行继续使用Requsest来跟进URL,callback=是指定回调函数。
获取小说的信息

获取地址:http://www.23us.so/list/1_1.html

    """获取小说的大概信息"""
    def get_name(self, response):
        novellist = BeautifulSoup(response.text, 'lxml').find_all('tr', bgcolor='#FFFFFF')
        for novel in novellist:
            novelname = novel.find('a').text    # 获取小说的名字
            novelurl = novel.find('a')['href']  # 小说的url
            yield Request(novelurl, callback=self.get_chapterurl, meta={'name': novelname, 'url': novelurl})    # meta传递额外参数
  1. 多了一个meta这么一个字典,这是Scrapy中传递额外数据的方法。
获取小说的具体信息
    """获取小说的具体信息"""
    def get_chapterurl(self, response):
        item = ScrapyDemoItem()     # 将倒入的item文件实例化
        item['name'] = str(response.meta['name'])   # 获取上个函数返回的小说名字和url
        item['novelurl'] = response.meta['url']

        soup = BeautifulSoup(response.text, 'lxml')     # 获取小说url地址的内容
        category = soup.find('table').find('a').text    # 获取小说的类型
        author = soup.find('table').find_all('td')[1].text.lstrip()     # 获取小说的作者
        bash_url = soup.find('p', class_='btnlinks').find('a', class_='read')['href']   # 最新章节的连接
        name_id = bash_url.split('/')[-2]   # 小说的id来判断在数据库的存取
        item['category'] = category
        item['author'] = author
        item['name_id'] = name_id
        return item
进行小说信息的存取
  1. 首先为了能好区分框架自带的Pipeline,我们把MySQL的Pipeline单独放到一个目录里面。
  2. 新建了一个mysqlpipelines的文件夹,我们所有的MySQL文件都放在这个目录。
  3. pipelines.py 这个是我们写存放数据的文件
  4. sql.py 需要的sql语句
    1-w271
创建Mysql表
DROP TABLE IF EXISTS `dd_name`;
CREATE TABLE `dd_name` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `xs_name` varchar(255) DEFAULT NULL,
  `xs_author` varchar(255) DEFAULT NULL,
  `category` varchar(255) DEFAULT NULL,
  `name_id` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8mb4;
在settings.py文件中定义好MySQL的配置文件
MYSQL_HOSTS = '127.0.0.1'
MYSQL_USER = 'root'
MYSQL_PASSWORD = 'qwer1234'
MYSQL_PORT = 3306
MYSQL_DB = 'python'

使用的是pymysql
下载命令:pip install pymysql

sql.py文件
import pymysql
from scrapy_demo import settings

MYSQL_HOSTS = settings.MYSQL_HOSTS
MYSQL_USER = settings.MYSQL_USER
MYSQL_PASSWORD = settings.MYSQL_PASSWORD
MYSQL_PORT = settings.MYSQL_PORT
MYSQL_DB = settings.MYSQL_DB

#   打开数据库连接
db = pymysql.connect(host=MYSQL_HOSTS, user=MYSQL_USER, password=MYSQL_PASSWORD, db=MYSQL_DB, port=MYSQL_PORT, use_unicode=True, charset="utf8")

# 使用cursor()方法获取操作游标
cur = db.cursor()


class Sql:

    @classmethod
    def insert_dd_name(cls, xs_name, xs_author, category, name_id):
        sql = 'insert into dd_name (xs_name, xs_author, category, name_id) values (%(xs_name)s, %(xs_author)s, %(category)s, %(name_id)s)'
        value = {
            'xs_name' : xs_name,
            'xs_author' : xs_author,
            'category' : category,
            'name_id' : name_id
        }
        cur.execute(sql, value)
        db.commit()

    """会查找name_id这个字段,如果存在则会返回 1 不存在则会返回0"""
    @classmethod
    def select_name(cls, name_id):
        sql = "select exists(select 1 from dd_name where name_id = %(name_id)s)"
        value = {
            'name_id' : name_id
        }
        cur.execute(sql, value)
        return cur.fetchall()[0]
pipeline文件
from .sql import Sql
from scrapy_demo.items import ScrapyDemoItem
from scrapy_demo.items import DcontentItem
class ScrapyDemoPipeline(object):
    def process_item(self, item, spider):
        if isinstance(item, ScrapyDemoItem):
            name_id = item['name_id']
            ret = Sql.select_name(name_id)
            if ret[0] == 1:
                print('已经存在小说')
            else:
                xs_name = item['name']
                xs_author = item['author']
                category = item['category']
                Sql.insert_dd_name(xs_name, xs_author, category, name_id)
                print('开始存取小说')
  1. process_item中的item和spider这两个参数不能缺少
  2. isinstance(item, ScrapyDemoItem)判断类型是否相同
  3. 我们要启用这个Pipeline在settings中作如下设置:
ITEM_PIPELINES = {
   # 'scrapy_demo.pipelines.ScrapyDemoPipeline': 300,
   'scrapy_demo.mysqlpipelines.pipelines.ScrapyDemoPipeline': 1,
}

PS: scrapy_demo(项目目录).mysqlpipelines(自己建立的MySQL目录).pipelines(自己建立的pipelines文件).DingdianPipeline(其中定义的类) 后面的 1 是优先级程度(1-1000随意设置,数值越低,组件的优先级越高)

运行程序存取小说目录

运行的是entrypoint.py文件
2

收获:

  1. 在使用requests来分析http://www.23us.so/xiaoshuo/18747.html的时候。
url = 'http://www.23us.so/xiaoshuo/18747.html'
# response = requests.get(url).content.decode('utf-8')
response = requests.get(url).text
print(response)

获取的是乱码,其实是ASCII格式只能显示英文不能显示中文。
text返回的unicode类型的数据。
content返回的是bytes,二进制类型。
所以在获取页面信息的时候,将content直接解码为utf-8

response = requests.get(url).content.decode('utf-8')

2.在获取存取小说内容的时候 地址

内容有回车和制表符号,获取的内容是短短续续的。
**提示**:常用空格是\x20标准ASCII可见字符 0x20~0x7e 范围内,而 \xa0 属于 latin1 (ISO/IEC_8859-1)中的扩展字符集字符,代表空白符nbsp(non-breaking space)。 latin1 字符集向下兼容 ASCII ( 0x20~0x7e )。
使用方法来去除掉制表符和回车
s = ''.jion(s.split())
join(): 连接字符串数组。将字符串、元组、列表中的元素以指定的字符(分隔符)连接生成一个新的字符串。
split():split方法中不带参数时,表示分割所有换行符、制表符、空格。

3.在数据库存储数据遇到问题:

python3 pymysql 'latin-1' codec can't encode character 错误 问题解决
# 打开数据库连接
db = pymysql.connect("localhost","root","00000000","TESTDB" ,use_unicode=True, charset="utf8")
原因:pymysql 正常情况下会尝试将所有的内容转为latin1字符集处理,latin1格式的表结构,不可以存放中文,因为是单字节编码,但是向下兼容ASCII。
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值