Scrapy爬取顶点小说网

Scrapy爬取小说

爬取目标:顶点小说网

1、Scrapy的安装

pip install scrapy

2、Scrapy的介绍

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

项目结构
  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的名字。

模块作用介绍
  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中取消下面几行的注释:

    这几行注释的作用是,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语句
创建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文件

收获:

  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。

你可能感兴趣的:(Python爬虫)