写在前面
经过之前的学习,整个爬虫项目已经可以完成最基本的爬取操作,并且可以将数据存储至数据库,这一部分从提取二级链接入手,更加深入的探究内置爬虫文件。
开始一个新的项目文件
考虑到科创项目的需要,目前需要完成的任务便是提供一个关键词,爬取通过该关键词搜索到的所有文章,这就不可避免的要涉及自动获取下一页的url地址,重新丢回调度器继续爬取这一核心操作,因此新建一个项目,同时也算是对之前完成的一系列操作的复习。
大致思路是以搜索Scrapy关键词进入的页面url作为整个项目start_urls初始地址,按照之前的思路,爬取搜索到的文章的链接,并且获得作者信息,一并存储至数据库。这里和之前的内容基本完全一致,无非就是增加了两个信息的合并,所以这里不再做详细说明,如果有问题同样欢迎发送至我的邮箱(sunzhihao_future@nuaa.edu.cn),可以一起交流。
这个新开始的项目现在已经上传至CSDN资源以及百度网盘,同时附上项目的主要文件内容。
链接:https://pan.baidu.com/s/1nbYtvxKs1pinRrcjkWP8sA
提取码:4blq
search.py_(爬虫文件)
import scrapy
from ..items import SearchScrapyItem
class SearchSpider(scrapy.Spider):
name = "search"
start_urls = ['https://so.csdn.net/so/search/s.do?q=Scrapy&t=blog&o=&s=&l=']
def parse(self, response):
print(response)
search = SearchScrapyItem()
title_list = response.xpath("/html/body/div[@class='main-container']"
"/div[@class='con-l']/div[@class='search-list-con']"
"/dl[@class='search-list J_search']"
"/dt/div[@class='limit_width']/a[1]").extract()
writer_list = response.xpath("/html/body/div[@class='main-container']"
"/div[@class='con-l']/div[@class='search-list-con']"
"/dl[@class='search-list J_search']"
"/dd[@class='author-time']/span[@class='author']/a/text()").extract()
for i,j in zip(title_list, writer_list):
search['title'] = i
search['writer'] = j
yield search
# print(i, ":", j)
pipelines.py_(管道文件)
# -*- coding: utf-8 -*-
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
import sqlite3
class SearchScrapyPipeline(object):
def open_spider(self, spider):
self.con = sqlite3.connect("search.sqlite")
self.cu = self.con.cursor()
def process_item(self, item, spider):
print(spider.name, 'pipelines')
insert_sql = "insert into article (title, writer) values ('{}', '{}')".format(item['title'], item['writer'])
print(insert_sql)
self.cu.execute(insert_sql)
self.con.commit()
return item
def spider_close(self, spider):
self.con.close()
items.py_
# -*- coding: utf-8 -*-
# Define here the models for your scraped items
#
# See documentation in:
# http://doc.scrapy.org/en/latest/topics/items.html
import scrapy
class SearchScrapyItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
title = scrapy.Field()
writer = scrapy.Field()
# pass
访问二级链接
添加注释的习惯
从这一部分开始,感觉代码会开始变得复杂,因此想到添加注释的重要性,因此在这里提醒自己,也提醒大家能养成及时在繁杂位置添加注释的好习惯,这既是对自己写过的代码的一种回顾性的检验,同时也能在未来改进代码时减少很多工作量,除此之外,在别人阅读代码时,能够更加轻松的了解整个代码的思路。
获得完整的二级链接
在开头时已经说过,获得下一页的url地址是现在想要完成的关键任务,这里不禁会抛出几个问题,网页中“下一页”按钮背后隐藏的二级链接是什么?如何获得一个完整的“下一页”url地址?
首先来到整个项目开始的初始地址(https://so.csdn.net/so/search/s.do?q=Scrapy&t=blog&u= ),打开开发者工具(Chrome浏览器快捷键为ctrl + shift + I),如下图所示。
选择位于右侧分栏左上角的选择工具,可以直接定位到鼠标指示的元素,如下图所示。
因此可以得知,这个链接到下一页的按钮在标签a下,可以通过独有的类名搜索到这个指向下一页的按钮,进而获得有价值的链接,即其href值,这里为下述字符串。
?p=2&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0”
关闭开发者工具,点击该按钮,进入到搜索的下一页,得到完整的url地址如下:
https://so.csdn.net/so/search/s.do?p=2&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0
再将项目开始的初始地址拿来作比较:
通过观察比较,不难发现,它们拥有共同的host_name(主地址),而一个完整的导向下一页的链接正是由host_name追加上按钮元素的href值拼接而成,这里运用字符串的format拼接很容易实现,现在对search.py爬虫文件中的内容做以下改动
import scrapy
from ..items import SearchScrapyItem
class SearchSpider(scrapy.Spider):
name = "search"
start_urls = ['https://so.csdn.net/so/search/s.do?q=Scrapy&t=blog&o=&s=&l=']
# 设置host_name为url的format拼接做准备
host_name = 'https://so.csdn.net/so/search/s.do{}'
def parse(self, response):
print(response)
search = SearchScrapyItem()
# 此语句用来搜索特定标签下的特定类名的元素,最后一层@href指示该元素的href值
# 返回的为列表,我们需要的为列表中的首元素
next_links = response.xpath(".//a[@class='btn btn-xs btn-default btn-next']/@href").extract()
# 这里做一个简单的判断,防止没有搜索到该元素但是程序仍然运行出现错误
if len(next_links) > 0:
# 进行完整url的拼接
next_link = self.host_name.format(next_links[0])
print("-----next_link-----", next_link)
else:
pass
这里用到了通过id和class查询xpath,可能第一次看起来有些奇怪,附上一个详细介绍这一部分内容的文档,可以做进一步了解。
Xpath教程-通过ID和Class检索
接下来执行该爬虫,查看程序运行情况。
C:\Users\Lenovo\Desktop\search_scrapy>scrapy crawl search
…
…
<200 https://so.csdn.net/so/search/s.do?q=Scrapy&t=blog&o=&s=&l=>
-----next_link----- https://so.csdn.net/so/search/s.do?p=2&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0
…
程序没有报错,并且成功得到了我们需要的url地址。
抓取所有页面的数据
接下来结合之前已经完成的爬取数据的工作,完全可以实现爬取到所有搜索到的文章,思路如下:
根据以上思路,可以很清晰的写出代码,对search.py爬虫文件中的内容做以下改动
import scrapy
from ..items import SearchScrapyItem
class SearchSpider(scrapy.Spider):
name = "search"
start_urls = ['https://so.csdn.net/so/search/s.do?q=Scrapy&t=blog&o=&s=&l=']
# 设置host_name为url的format拼接做准备
host_name = 'https://so.csdn.net/so/search/s.do{}'
def parse(self, response):
print(response)
search = SearchScrapyItem()
# 此语句用来搜索特定标签下的特定类名的元素,最后一层@href指示该元素的href值
# 返回的为列表,我们需要的为列表中的首元素
next_links = response.xpath(".//a[@class='btn btn-xs btn-default btn-next']/@href").extract()
# 这里做一个简单的判断,防止没有搜索到该元素但是程序仍然运行出现错误
if len(next_links) > 0:
# 进行完整url的拼接
next_link = self.host_name.format(next_links[0])
print("-----next_link-----", next_link)
# 进行回调
yield scrapy.Request(next_link, callback=self.parse)
else:
pass
title_list = response.xpath("/html/body/div[@class='main-container']"
"/div[@class='con-l']/div[@class='search-list-con']"
"/dl[@class='search-list J_search']"
"/dt/div[@class='limit_width']/a[1]/@href").extract()
writer_list = response.xpath("/html/body/div[@class='main-container']"
"/div[@class='con-l']/div[@class='search-list-con']"
"/dl[@class='search-list J_search']"
"/dd[@class='author-time']/span[@class='author']/a/text()").extract()
for i,j in zip(title_list, writer_list):
search['title'] = i
search['writer'] = j
yield search
这里用到了yield,是一个生成器,起到返回的作用,但是不停止函数的执行,同时还有节约内存的优点。
接下来执行该爬虫,查看程序运行情况。
C:\Users\Lenovo\Desktop\search_scrapy>scrapy crawl search
…
…
2019-04-17 18:58:25 [scrapy.core.scraper] DEBUG: Scraped from <200 https://so.csdn.net/so/search/s.do?p=18&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0>
{‘title’: ‘https://blog.csdn.net/beyond_f/article/details/74298569’,
‘writer’: ‘beyond_f’}
search pipelines
insert into article (title, writer) values (‘https://blog.csdn.net/qq_40390825/article/details/82715320’, ‘qq_40390825’)
2019-04-17 18:58:25 [scrapy.core.scraper] DEBUG: Scraped from <200 https://so.csdn.net/so/search/s.do?p=18&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0>
{‘title’: ‘https://blog.csdn.net/qq_40390825/article/details/82715320’,
‘writer’: ‘qq_40390825’}
2019-04-17 18:58:25 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://so.csdn.net/so/search/s.do?p=19&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0> (referer: https://so.csdn.net/so/search/s.do?p=18&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0)
<200 https://so.csdn.net/so/search/s.do?p=19&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0>
-----next_link----- https://so.csdn.net/so/search/s.do?p=20&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0
search pipelines
insert into article (title, writer) values (‘https://blog.csdn.net/weixin_33716941/article/details/86866447’, ‘weixin_33716941’)
2019-04-17 18:58:26 [scrapy.core.scraper] DEBUG: Scraped from <200 https://so.csdn.net/so/search/s.do?p=19&q=Scrapy&t=blog&domain=&o=&s=&u=&l=&rbg=0>
{‘title’: ‘https://blog.csdn.net/weixin_33716941/article/details/86866447’,
‘writer’: ‘weixin_33716941’}
…
执行的时间可能会比较长,执行结束后仔细观察会发现一共爬取到了20页,和网页里看到的是一致的,没有问题,我们打开数据库,可以发现爬取到的200条数据已经成功存储。
以上便是这部分的内容。
当前项目存档
从这一篇文章开始,以后每次完成的项目我都会保存一次,相当于记录自己的项目进展,同时也为以后万一需要归档做准备,大家也可以拿来做进一步的比较。
进行到这里的整个项目文件已经上传至我的百度网盘,如有需要可以自行下载。
版本:search_scrapy_v4.1
链接:https://pan.baidu.com/s/1Q1RpAZ5FwhbDE5QvvvFU0g
提取码:oyb4