(PS:图片取自百度搜图,图片上的水印有些糊,看不清来源了.)这个图解释的很详细,后面的理解均在该图上展开。
要了解Scrapy框架,从这个图出发,理解很轻松.
1.新建scrapy项目:
scrapy startproject [项目名]
2.在项目内新建Spider(这里定义了爬取的逻辑和解析网页的规则)
scrapy genspider [Spider名称] [要爬取的域名]
3.启动spider
scrapy crawl [Spider名称]
位于Downloader
(下载器)和Engine
(引擎)中间,处理两者之间的请求和响应。
三个核心方法
process_request(request,spider)
process_response(request,response,spider)
process_exception(request,exception,spider)
作用情景
1.在响应(response)内容发送给Spider之前对response进行处理
2.在Spider生成的request发送给Scheduler之前,对request进行处理
3.在Spider生成的Item发送给Item Pipeline之前,对Item进行处理。
四个核心方法
process_spider_input(response,spider)
process_spider_output(response,result,spider)
process_spider_exception(response,exception,spider)
process_start_requests(start_requests,spider)
主要功能
1.清理HTML数据
2.验证爬取数据,检查爬取字段
3.查重并丢弃重复内容
4.将爬取结果保存到数据库
核心方法
open_spider(spider)
close_spider(spider)
from_crawler(cls,crawler)
这篇文章解释的很好,建议看一下:参考资料
简单来说,假如构建一个函数,调用这个函数时不立即执行,而是返回返回一个generate对象,在你需要它执行时主动控制其启动。
generate对象下有__next__
这样一个方法,调用它就相当于启动generate(ps:for循环中默认直接启动)
见解:
为什么scrapy需要用到yield来构建生成器对象?
原因:为了各模块之间可以协同操作,共同对同一数据进行处理。假设有A、B、C、D四个模块,数据段X和Y,模块之间协作顺序为ABCD依次进行,ABCD各通过yield产生一个生成器对象。A启动生成器对X进行操作,然后交给B,依次进行。在此期间Y不会被A加载进来,只有等X被处理完毕,A才会启动生成器加载Y,依次对Y进行处理。
就像工厂的流水线上,有很对道工序,一个零件接受完一道工序后才能去下一道工序,生成器就相当于控制流水线转动的开关。
借助Downloader Middleware
暂且称其为下载中间件
例如设置随机的User-Agent
在项目的middlewares.py
文件中添加类RandomUserAgentMiddleware
'''
引用自:https://github.com/Python3WebSpider/ScrapyDownloaderTest/blob/master/scrapydownloadertest/middlewares.py
'''
import random
from scrapy import Request
class RandomUserAgentMiddleware():
def __init__(self):
self.user_agents = [
'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)',
'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2',
'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1'
]
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(self.user_agents)
在settings.py
文件中的Downloader Middleware
字段内注册这个方法使之生效
DOWNLOADER_MIDDLEWARES = {
'scrapydownloadertest.middlewares.RandomUserAgentMiddleware': 543,
}
ajax是asynchronous javascript and XML的简写,中文翻译是异步的javascript和XML,这一技术能够向服务器请求额外的数据而无须卸载页面,会带来更好的用户体验
ajax技术的核心是XMLHttpRequest对象(简称XHR)
响应到的数据格式为json
第一步:编写Spider程序
class ImagesSpider(scrapy.Spider):
name = '' #Spider名称
allowed_domains = ['image.so.com'] #Spider作用的域名
start_urls = ['http://image.so.com/'] #Spider在启动时爬取的url列表
def start_requests(self): #用于向网站发起请求
data={'ch':'photography','listtype':'new',} #用于构建连接发起ajax请求的参数
base_url='https://image.so.com/zjl?' #参数会和base_url组成新的连接用于发起请求
for page in range(1,self.settings.get('MAX_PAGE')+1): #控制请求次数,这里设置的MAX_PAGE是50,也就是会发起50次请求
data['sn']=page*30
params=urlencode(data) #将字典转化为url格式的字符串
print(params)
url = base_url + params #合成链接
yield Request(url, self.parse) #返回一个生成器对象
def parse(self, response):
result = json.loads(response.text) #将想用内容从json格式转换为python数据格式,这里应该是字典
for image in result.get('list'):
item = ImageItem() #爬虫项目对象
item['id'] = image.get('id') #将获取到的字段传递给项目
item['url'] = image.get('qhimg_url')
item['title'] = image.get('title')
item['thumb'] = image.get('qhimg_thumb')
yield item
第二步:编写Item,定义数据库名,要爬取数据的字段名
class ImageItem(Item):
collection = table = 'images' #collection用于Mongodb,table用于mysql
id = Field()
url = Field()
title = Field()
thumb = Field()
第三步:编写Pipelines
用于将数据写入数据库或下载到本地
#用于将数据写入Mongodb
class MongoPipeline(object):
def __init__(self, mongo_uri, mongo_db):
self.mongo_uri = mongo_uri
self.mongo_db = mongo_db
@classmethod
def from_crawler(cls, crawler):
return cls(
mongo_uri=crawler.settings.get('MONGO_URI'),
mongo_db=crawler.settings.get('MONGO_DB')
)
def open_spider(self, spider):
self.client = pymongo.MongoClient(self.mongo_uri)
self.db = self.client[self.mongo_db]
def process_item(self, item, spider):
name = item.collection
self.db[name].insert(dict(item))
return item
def close_spider(self, spider):
self.client.close()
#用于将数据写入mysql
class MysqlPipeline():
def __init__(self, host, database, user, password, port):
self.host = host
self.database = database
self.user = user
self.password = password
self.port = port
@classmethod
def from_crawler(cls, crawler):
return cls(
host=crawler.settings.get('MYSQL_HOST'),
database=crawler.settings.get('MYSQL_DATABASE'),
user=crawler.settings.get('MYSQL_USER'),
password=crawler.settings.get('MYSQL_PASSWORD'),
port=crawler.settings.get('MYSQL_PORT'),
)
def open_spider(self, spider):
self.db = pymysql.connect(self.host, self.user, self.password, self.database, charset='utf8',
port=self.port)
self.cursor = self.db.cursor()
def close_spider(self, spider):
self.db.close()
def process_item(self, item, spider):
print(item['title'])
data = dict(item)
keys = ', '.join(data.keys())
values = ', '.join(['%s'] * len(data))
sql = 'insert into %s (%s) values (%s)' % (item.table, keys, values)
self.cursor.execute(sql, tuple(data.values()))
self.db.commit()
return item
#用于将数据下载到本地
class ImagePipeline(ImagesPipeline):
def file_path(self, request, response=None, info=None):
url = request.url
file_name = url.split('/')[-1]
return file_name
def item_completed(self, results, item, info):
image_paths = [x['path'] for ok, x in results if ok]
if not image_paths:
raise DropItem('Image Downloaded Failed')
return item
def get_media_requests(self, item, info):
yield Request(item['url'])
相较于selenium的优点:
Scrapy对接selenium时需要自己实现Downloader Middleware,而Scrapy-Splash直接提供了多个高效的Downloader Middleware,在需要时直接调用即可。
#Splash运行的服务器地址
SPLASH_URL='http://localhost:8050'
DOWNLOADER_MIDDLEWARES={
'scrapy_splash.SplashCookiesMiddleware':723,
'scrapy_splash.SplashMiddleware':725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware':810
}
SPIDER_MIDDLEWARES={
'scrapy_splash.SplashDeduplicateArgsMiddleware':100,
}
方法1:直接生成SplashRequest对象并传递相应的参数,Scrapy会将此请求转发给Splash,Splash对页面进行渲染加载,然后再将渲染结果传递回来。此时Response的内容就是渲染完成的页面结果了。
yield SplashRequest(url,self.parse_result,
args={
..."用于传递渲染参数"
},
endpoint='render.json',
splash_url='' ,