Scrapy爬取小说 爬取目标:顶点小说网
1、Scrapy的安装
pip install scrapy
2、Scrapy的介绍
- 创建项目
scrapy startproject xxxxxx项目名字
项目结构
- items.py 负责数据模型的建立,类似实体类。
- middlewares.py 自己定义的中间件
- pipelines.py 负责对spider返回数据的处理
- settings.py 复制对整个爬虫的配置
- spiders 目录负责存放继承自scrapy的爬虫类
- scrapy.cfg scrapy基础配置
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'scrapy_demo'])
注意!第二行中代码中的前两个参数是不变的,第三个参数请使用自己的spider的名字。
模块作用介绍
- Scrapy Engine: 这是引擎,负责Spiders、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等等。
- Scheduler(调度器): 它负责接受引擎发送过来的requests请求,并按照一定的方式进行整理排列,入队、并等待Scrapy Engine(引擎)来请求时,交给引擎。
- Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spiders来处理。
- Spiders:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)。
- Item Pipeline:它负责处理Spiders中获取到的Item,并进行处理,比如去重,持久化存储(存数据库,写入文件,总之就是保存数据用的)。
- Downloader Middlewares(下载中间件):你可以当作是一个可以自定义扩展下载功能的组件。
- Spider Middlewares(Spider中间件):你可以理解为是一个可以自定扩展和操作引擎和Spiders中间‘通信‘的功能组件(比如进入Spiders的Responses; 和从Spiders出去的Requests)
- 第一件事情:是在items.py文件中定义一些字段,这些字段用来临时存储你需要保存的数据。方便后面保存数据到其他地方,比如数据库 或者 本地文本之类的。
- 第二件事情在spiders文件夹中编写自己的爬虫
- 第三件事情在pipelines.py中存储自己的数据
- 还有一件事情,不是非做不可的,就settings.py文件 并不是一定要编辑的,只有有需要的时候才会编辑。
- 建议一点:在大家调试的时候建议大家在settings.py中取消下面几行的注释:
这几行注释的作用是,Scrapy会缓存你有的Requests!当你再次请求时,如果存在缓存文档则返回缓存文档,而不是去网站请求,这样既加快了本地调试速度,也减轻了网站的压力。
第一步:在items.py定义爬取的字段
import scrapyclass 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)
- name就是我们在entrypoint.py文件中的第三个参数。
- 定义了一个allowed_domains;这个不是必须的;但是在某写情况下需要用得到,比如使用爬取规则的时候就需要了;它的作用是只会跟进存在于allowed_domains中的URL。不存在的URL会被忽略。
- 注意最后一行调用的不是requests,而是导入的Request包,来跟进我们的URL(并将返回的response作为参数传递给self.parse, 嗯!这个叫回调函数!)
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)
- 参数response是start_requests中最后使用生成器返回Request()将返回的response作为parse的参数。
- 最后一行继续使用Requsest来跟进URL,callback=是指定回调函数。
"""获取小说的大概信息"""
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传递额外参数
- 多了一个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
进行小说信息的存取
- 首先为了能好区分框架自带的Pipeline,我们把MySQL的Pipeline单独放到一个目录里面。
- 新建了一个mysqlpipelines的文件夹,我们所有的MySQL文件都放在这个目录。
- pipelines.py 这个是我们写存放数据的文件
- sql.py 需要的sql语句
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 settingsMYSQL_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 = https://www.it610.com/article/{'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 = https://www.it610.com/article/{'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('开始存取小说')
- process_item中的item和spider这两个参数不能缺少
- isinstance(item, ScrapyDemoItem)判断类型是否相同
- 我们要启用这个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文件
收获:
- 在使用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)
【Scrapy爬取顶点小说网】获取的是乱码,其实是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爬pixiv排行榜
- python爬虫|看一小伙如何使用 python爬虫来算命()
- 会计转行能做什么(看完这个故事,让你更清醒)
- 爬虫系列(数据标准化)
- #Python爬虫#Item Pipeline介绍(附爬取网站获取图片到本地代码)
- Scrapy爬取小说简单逻辑
- Python爬虫,私活接单记录,假日到手5500美滋滋
- 新selenium模拟登录知乎
- 渣本零基础努力自学python,半年成功上岸,良心分享学习心得和踩坑经历