一、目标地址:www.23us.so/
二、准备工作 1.开发环境 MacOS + PyCharm + Python3.5.3 + Scrapy + MySQL 2.安装Scrapy和MySQL自行解决
三、开始建项目和编写爬虫 在终端新建scrapy项目
scrapy startproject dingdian
复制代码
- 因为我们要将数据保存到MySQL数据库中,所以在这里需要自定义MySQL的Pipeline,用PyCharm打开项目,在项目文件夹中新建一个python模块‘mysqlpipelines’,区分框架自带的Pipeline。
- 在spiders文件夹下创建我们自己的spider(dingdian)
- 新建一个run.py文件,用作运行爬虫的入口
run.py的内容,'dingdian'是spider的唯一名称,在定义spider的时候定义
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'dingdian'])
复制代码
项目结构
建立模型item,在items.py中写入
import scrapy
class DingdianItem(scrapy.Item):
# define the fields for your item here like:
name = scrapy.Field()
author = scrapy.Field()
novelurl = scrapy.Field()
# 状态
serialstatus = scrapy.Field()
# 字数
serialnumber = scrapy.Field()
# 类别
category = scrapy.Field()
# 编号
name_id = scrapy.Field()
class DcontentItem(scrapy.Item):
# 小说编号
id_name = scrapy.Field()
# 章节内容
chaptercontent = scrapy.Field()
# 用于绑定章节顺序
num = scrapy.Field()
# 章节地址
chapterurl = scrapy.Field()
# 章节名字
chaptername = scrapy.Field()
复制代码
然后我们看一下入口地址 www.23us.so/
当然对于上面的地址,通过base_url + '_d'的方式请求
import re
import scrapy
from scrapy import Request
from bs4 import BeautifulSoup
from dingdian.items import DingdianItem, DcontentItem
from dingdian.mysqlpipelines.sql import Sql
class MySpider(scrapy.Spider):
name = "dingdian"
allowed_domains = ['23us.so']
base_url = 'https://www.23us.so/list/'
def start_requests(self):
for i in range(1, 10):
url = self.base_url + str(i) + '_1' + '.html' #小说分类的url
yield Request(url, self.parse)
# 全本
yield Request('https://www.23us.so/full.html', callback=self.parse)
复制代码
对于上面的代码,创建一个类 Myspider,这个类继承自scrapy.Spider,定义name:dingdian (请注意,这name就是在run.py文件中的第三个参数!),此Name的名字在整个项目中有且只能有一个,名字不可重复!
定义了一个allowed_domains;这个不是必须的,但是在某些情况下需要用得到,比如使用爬取规则的时候就需要了,它的作用是只会跟进存在于allowed_domains中的URL,不存在的URL会被忽略。使用字符串拼接的方式实现了上面发现的小说分类的所有URL。
最后使用parse函数接受上面request获取到的response,返回的response中的url便是每个小说分类的链接,每个分类下有很多页的内容,我们需要拿到页码
def parse(self, response):
max_num = response.css('div.pagelink a.last::text').extract_first()
for num in range(1, int(max_num) + 1):
next_page = str(response.url)[:-7] + '_' + str(num) + '.html'
if next_page is not None:
yield Request(next_page, callback=self.get_name)
复制代码
然后通过字符串拼接出每一页的链接next_page,当next_page存在的时候,便去请求,这里response.css是通过css选择器来查找标签的,查找标签的方式有很多,可以用css,xpath,或者BeautifulSoup。可以通过chrome或者Firefox快速获取到指定标签的css和xpath路径。在chrome中打开代码检查,点击这个图标
上面两个函数就彻底的把整个网站的所有小说的页面URL的提取出来了,并将每个页面的response交给了get_name函数处理。
def get_name(self, response):
tds = BeautifulSoup(response.text, 'lxml').find_all('tr', bgcolor='#FFFFFF')
for td in tds:
novelname = td.find('a').get_text()
novelurl = td.find('a')['href']
yield Request(novelurl, callback=self.get_chapterurl, meta={'name': novelname, 'url': novelurl})
复制代码
获取小说name和url,通过reques的meta将额外参数传递给get_chapterurl函数
def get_chapterurl(self, response):
item = DingdianItem()
item['name'] = str(response.meta['name']).replace('\xa0', '')
item['novelurl'] = response.meta['url']
category = response.css('table a::text').extract_first()
author = response.css('table td::text').extract()[1]
# 最新章节
bash_url = response.css('p.btnlinks a.read::attr(href)').extract_first()
name_id = str(bash_url).split('/')[-2]
item['category'] = str(category).replace('/', '')
item['author'] = str(author).replace('/', '')
item['name_id'] = name_id
yield item
yield Request(url=bash_url, callback=self.get_chapter, meta={'name_id': name_id})
复制代码
将需要的数据,复制给item[key] (注意这儿的Key就是前面在item文件中定义的那些字段)
注意!response.meta[key]:这个是提取从上一个函数传递下来的值。
return item 就是返回我们的字典了,然后Pipelines就可以开始对这些数据进行处理了。比如存储到MySQL中。
遍历每个小说的章节和之前的操作类似,都是查找标签,接下来我们说说通过Pipeline存储到MySQL的问题
新建两张表,一张存储 书名 + 作者 + 分类,另一张存储 章节名称 + 内容,我是用navicate for 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;
复制代码
DROP TABLE IF EXISTS `dd_chaptername`;
CREATE TABLE `dd_chaptername` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`xs_chaptername` varchar(255) DEFAULT NULL,
`xs_content` text,
`id_name` int(11) DEFAULT NULL,
`num_id` int(11) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2726 DEFAULT CHARSET=gb18030;
SET FOREIGN_KEY_CHECKS=1;
复制代码
在settings.py文件中定义好MySQL的配置文件,账户密码端口和数据库都根据自己本地的配置填写,‘DingDianBooks’是我为这个项目建的数据库,默认的端口一般都是3306.
# mysql
MYSQL_HOSTS = '127.0.0.1'
MYSQL_USER = 'root'
MYSQL_PASSWORD = '11111'
MYSQL_PORT = '3306'
MYSQL_DB = 'DingDianBooks'
复制代码
Python连接MySQL数据库需要下载另外的包,我这里使用的是mysql-connector,通过pip安装管理。
下面是我们的sql.py文件:
import mysql.connector
from dingdian import settings
# mysql
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
cnx = mysql.connector.connect(user=MYSQL_USER, password=MYSQL_PASSWORD, host=MYSQL_HOSTS, database=MYSQL_DB)
cur = cnx.cursor(buffered=True)
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)
cnx.commit()
# 去重
@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]
复制代码
初始化了一个MySQL的操作游标,将函数中的四个变量写入数据库,select_name是一个去重函数,这个函数会查找name_id这个字段,如果存在则会返回 1 不存在则会返回0。
sqi.py这一部分完成,现在开始写pipeline:
from .sql import Sql
from dingdian.items import DingdianItem, DcontentItem
class DingDianPipeline(object):
def process_item(self, item, spider):
if isinstance(item, DingdianItem):
name_id = item['name_id']
ret = Sql.select_name(name_id)
if ret[0] == 1:
print('已经存在')
pass
else:
xs_name = item['name']
xs_author = item['author']
category = item['category']
Sql.insert_dd_name(xs_name, xs_author, category, name_id)
复制代码
建立了一个DingdianPipeline的类,别忘了一定要继承object,定义了一个process_item函数并有item和spider这两个参数,这两个参数是必须的,当item中存在DingdianItem,先执行去重,然后就从item中取出值然后存入数据库。 另一种表的存取方式是类似的,详细的可以去看代码。
到此,真个爬虫差不多完成了,只需要在PyCharm中运行run.py便可以执行爬虫。