一、目标地址:
二、准备工作 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 executeexecute(['scrapy', 'crawl', 'dingdian'])复制代码
项目结构
建立模型item,在items.py中写入
import scrapyclass 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()复制代码
然后我们看一下入口地址
玄幻魔法:
武侠修真:
都市言情:
历史军事:
网游竞技:
科幻小说:
恐怖灵异:
女生小说:
其他:
全本:
当然对于上面的地址,通过base_url + '_d'的方式请求
import reimport scrapyfrom scrapy import Requestfrom bs4 import BeautifulSoupfrom dingdian.items import DingdianItem, DcontentItemfrom dingdian.mysqlpipelines.sql import Sqlclass 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中打开代码检查,点击这个图标
然后在网页上选择你要查看的内容,便会自动跳转到指定内容的html标签,然后在标签上右键便可复制css和xpath路径,这样取出的路径会比自己写的长,所以我还是选择自己写比较简洁。上面两个函数就彻底的把整个网站的所有小说的页面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.
# mysqlMYSQL_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.connectorfrom dingdian import settings# mysqlMYSQL_HOSTS = settings.MYSQL_HOSTSMYSQL_USER = settings.MYSQL_USERMYSQL_PASSWORD = settings.MYSQL_PASSWORDMYSQL_PORT = settings.MYSQL_PORTMYSQL_DB = settings.MYSQL_DBcnx = 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 Sqlfrom dingdian.items import DingdianItem, DcontentItemclass 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便可以执行爬虫。