parse()方法的工作机制
感谢脑补连接
-
因为使用的yield,而不是return。parse函数将会被当做一个生成器使用。scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型;
-
如果是request则加入爬取队列,如果是item类型则使用pipeline处理,其他类型则返回错误信息;
-
scrapy取到第一部分的request不会立马就去发送这个request,只是把这个request放到队列里,然后接着从生成器里获取;
-
parse()方法作为回调函数(callback)赋值给了Request,指定parse()方法来处理这些请求 scrapy.Request(url, callback=self.parse);
-
Request对象经过调度,执行生成 scrapy.http.response()的响应对象,并送回给parse()方法,直到调度器中没有Request(递归的思路);
-
取尽之后,parse()工作结束,引擎再根据队列和pipelines中的内容去执行相应的操作;
-
程序在取得各个页面的items前,会先处理完之前所有的request队列里的请求,然后再提取items;
-
这一切的一切,Scrapy引擎和调度器将负责到底。
CrawlSpider 爬虫
之前使用普通的spider ,我们是自己再解析完整个页面后获取下一页的url, 然后从新发送一个请求。有时候我们想要这么
做,只要满足某个条件的url ,都给我进行爬取。那么这个时候我们就可以通过 CrawlSpider 帮助实现。 它继承自Spider ,只
不过实在之前的基础上增加了新的功能,可以定义爬取url 的规则,以后 scrapy 再碰到任何满足条件的url 都会进行爬取,
而不用手动 yield Request.
- 创建
scrapy genspider -t crawl name domain
- LinkExtractors 链接提取器:
使用 linkextractors 可以不用程序猿自己提取想要的url, 然后发送请求。这些工作都可以交给这个链接提取器。他会在所有爬的页面中找到满足规则的url ,实现自动爬取。
classscrapy.linkextractors.lxmlhtml.LxmlLinkExtractor(allow=(), deny=(), allow_domains=(), deny_domains=(), deny_extensions=None, restrict_xpaths=(), restrict_css=(), tags=(‘a’, ‘area’), attrs=(‘href’, ), canonicalize=False, unique=True, process_value=None, strip=True)
主要参数解释:更多的去看官方的帮助文档吧
- allow 一个正则表达式,所有满足这个正则表达式的url 都会被提取。如果是空,则匹配所有链接。
- deny 一个正则,满足条件的url 会被排除,优先级高于 allow ,如果是空,不会排除任何url.
- allow_domains 一个字符串或列表,提取这里面指定的域名里的url
- deny_domains 里面的url 都不会提取
- restrict_xpath :xpath 的语法或语法的列表,只会提取它扫描出来的链接,过滤作用。
- Rule规则类
classscrapy.spiders.Rule(link_extractor=None, callback=None, cb_kwargs=None, follow=None, process_links=None, process_request=None, errback=None)
- link_extractor :一个Link Extractor 对象,用来爬取定义后的链接,每一个链接将生成一个request 对象,保存再 link_text 的key 下。如果没有定义,将提取所有链接。
- callback : 满足这个条件的url,应该要执行那个回调函数。因为 crawspider 使用了parse作为回调函数,因此不要覆盖parse 作为回调函数,否则这个爬虫就不会运行
- follow:指定根据该规则从response 中提取的链接是否需要跟进
- process_links: 从link_extractor 中获取到的链接后会传递给这个函数,用来过滤不需要爬取的链接。
案例:猎云网
需求:实现猎云网网站的文件数据爬虫,需要保存标题,发布时间,内容,原创url字段,然后异步保存到mysql 数据库中。
- 由于我们再解析网页的时候会尝试很多次,但是使用scrapy 框架会自动搞很对网站,所以我们可以再shell 里把相关的语法写好。
shell
这是一个命令行交互模式
通过scrapy shell url地址进入交互模式
这里我么可以通过css选择器以及xpath选择器获取我们想要的内容
然后再下面写就行啦。。。
- 异步保存MySQL数据
可以去瞅瞅我以前写的python操作MySQL数据库啊
当然如果我们使用普通的方法效率是很低的,所以来试试异步。。。
异步:不等任务执行完,直接执行下一个任务。
同步:一定要等任务执行完了,得到结果,才执行下一个任务。
- 使用
twisted.enterprise.adbapi
来创建连接池 - 使用
runlnteraction
来运行插入sql 语句的函数 - 再插入sql 语句的函数中,第一个非self的参数就是cursor 对象,使用这个对象执行sql 语句。
开始:
- 创建项目:
scrapy startproject leiyun
cd leiyun
scrapy genspider -t crawl lieyun_spider lieyunwang.com
2.修改settings:
除了不遵守机器人协议,请求头,打开管道之外还要配置我们的MySQL数据库
MYSQL_CONFIG={ # 配置数据库
'DRIVER':'pymysql', # 驱动
'HOST':'localhost',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '54567789',
'DATABASE':'猎云网' # 操作的数据库
}
再navicat建好表格
3. 确认需要提取的数据: item
import scrapy
class LieyunItem(scrapy.Item):
title = scrapy.Field()
content = scrapy.Field()
author = scrapy.Field()
pub_time = scrapy.Field()
origin = scrapy.Field()
- spider
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from ..items import LieyunItem
class LieyunSpiderSpider(CrawlSpider):
name = 'lieyun_spider'
allowed_domains = ['lieyunwang.com']
start_urls = ['https://www.lieyunwang.com/latest/p1.html']
rules = (
Rule(LinkExtractor(allow=r'/latest/p\d+.html'), follow=True), # 框架会根据允许的域名自动补上
# 提取页数的url,选择跟进否则就不会爬取多页,不需要回调函数,因为我们不在这些url里爬取数据
Rule(LinkExtractor(allow=r'/archives/\d+'),callback='parse_item',follow=False),
# 根据前一个规则提取到的页面url提取文章的链接,使用回调函数去解析数据然后爬取,不用跟进否则会吧那些推荐的文章也爬出来
)
def parse_item(self, response): # 以下的命令可以再 scrapy shell 中测试的
title = response.xpath(r'//h1[@class="lyw-article-title"]/text()').getall()
title = ''.join(title).strip()
pub_time = response.xpath(r'//h1[@class="lyw-article-title"]/span/text()').getall()
author = response.xpath(r'//div[@class="author-info"]/a/text()').get()
origin = response.url
content = response.xpath(r'//div[@class="main-text"]').get() # 不用text()了,省的再整格式了
item = LieyunItem(title=title,pub_time=pub_time,author=author,content=content,origin=origin)
return item # 也可以是return item,因为这个页面只有一个item,当有多个是还是用yield
- pipelines
@classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
from twisted.enterprise import adbapi # 异步的API ,
class LieyunPipeline(object):
def __init__(self,mysql_config):
self.dbpool = adbapi.ConnectionPool(mysql_config['DRIVER'], # 连接对象
host = mysql_config['HOST'],
port = mysql_config['PORT'],
user = mysql_config['USER'],
password = mysql_config['PASSWORD'],
db = mysql_config['DATABASE'],
charset = 'utf8')
@classmethod
def from_crawler(cls,crawler): # 获取setting 里的数据库配置信息
mysql_config = crawler.settings['MYSQL_CONFIG']
return cls(mysql_config) # 这个cls代表了这个类本身,再创建对象的时候,就会调用这个参数。
# 只要重写了这个方法,再以后创建对象的时候,会调用这个方法来获取pipeline 对象。
def process_item(self, item, spider):
result = self.dbpool.runInteraction(self.insert_item,item) # 运行,再调用insert_item时,会传入cursor 对象
result.addErrback(self.insert_error) # 容错处理
return item
def insert_item(self,cursor,item):
sql = 'insert into 猎云网数据(id,title,author,content,pub_time,origin) values(null,%s,%s,%s,%s,%s)'
args = (item['title'],item['author'],item['content'],item['pub_time'],item['origin'])
cursor.execute(sql,args)
def insert_error(self,failure):
print('出错了。。。。')
print(failure)
def close_spider(self,spider):
self.dbpool.close()
- run
from scrapy import cmdline
cmdline.execute("scrapy crawl lieyun_spider".split(" "))
。。。呀。。。为了找那个打错的字母捣鼓了好久,,,,,当我发现把from_crawler 写成了 from_crawier时,,,,@#$%@%#%#%#