Scrapy,Python开发的一个快速、高层次的屏幕抓取和web抓取框架,用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。--网络介绍
罗列推荐看的网址:
- Scrapy Tutorial 官方doc,比较推荐
- 极简中文入门介绍 可以用以简单了解
- github上的自发的翻译项目
- XPath教程
按教程安装完(这里推荐且只推荐使用conda方式安装,而非其它,诸如pip)后,常用的命令:
scrapy startproject projectName
scrapy genspider spiderName "xxx.html"
scrapy crawl spiderName
scrapy shell "url"
整体而言,要做的就是:先设计自己要爬取的item的结构体(items.py),同时设计对应的spider.py,在爬虫代码中,将爬到的内容填入item的字段后,将item交给pipeline处理,pipeline将这些爬取到的数据存储到文件,或者是数据库中。
具体流程也一并给出如下:
- 爬虫引擎获得初始请求开始抓取。
- 爬虫引擎开始请求调度程序,并准备对下一次的请求进行抓取。
- 爬虫调度器返回下一个请求给爬虫引擎。
- 引擎请求发送到下载器,通过下载中间件下载网络数据。
- 一旦下载器完成页面下载,将下载结果返回给爬虫引擎。
- 引擎将下载器的响应通过中间件返回给爬虫进行处理。
- 爬虫处理响应,并通过中间件返回处理后的items,以及新的请求给引擎。
- 引擎发送处理后的 items 到项目管道,然后把处理结果返回给调度器,调度器计划处理下一个请求抓取。
- 重复该过程(继续步骤1),直到爬取完所有的 url 请求。
建立爬虫项目之后,给出爬虫代码模板,第一个代码是最基本的爬虫,但是,这份代码实在是太简单了,只能爬一两个页面,如果是要爬多页数据,页里又有多条数据,可以下面的第二份代码:
"""第一份代码"""
import scrapy
class QuotesSpider(scrapy.Spider):
name = "quotes"
def start_requests(self):
urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
page = response.url.split("/")[-2]
filename = 'quotes-%s.html' % page
with open(filename, 'wb') as f:
f.write(response.body)
self.log('Saved file %s' % filename)
"""第二份代码"""
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
urls = ['https://www.xxx.com']
for url in urls:
yield scrapy.Request(url=url, callback=self.url_parse)
def url_parse(self, response):
"""
url_parser目的是获取目录页面上一个个页面的网址
"""
urls = response.xpath('...')
for url in urls:
# 对每个页面进行数据提取
yield scrapy.Request(url, callback=self.data_parse)
next_page = response.xpath('...').get()
if next_page is not None:
yield response.follow(next_page, callback=self.url_parse)
def data_parse(self, response):
"""
从最终的页面获取数据
"""
data = response.xpath('...')
l = ItemLoader(item=MyItem(), response=response)
l.add_value('data', data)
yield l.load_item()
其中name即当前爬虫的名称,也是今后启动它的唯一标识。爬虫回自动执行start_requests(self)函数,按代码对url发起请求,这是比较自定义的形式。如果为了偷懒,也可以不重写start_requests(),而是用下面这份代码来代替:
name = "quotes"
start_urls = [
'http://quotes.toscrape.com/page/1/',
'http://quotes.toscrape.com/page/2/',
]
这些就不赘述。总的来说,start_urls负责发起请求,请求之后的结果,会放入response函数中,并运行parse(self, response)函数,我们的目的就是从response中,利用各种解析工具,比如css或者xpath或者正则表达式来提取内容,提取之后,可以放入自定义的item,丢入pipeline来持久化(比如存入数据库),也可以直接parse完写入文件。
先在items.py 定义自己想要的item:
import scrapy
class Product(scrapy.Item):
name = scrapy.Field()
price = scrapy.Field()
stock = scrapy.Field()
last_updated = scrapy.Field(serializer=str)
然后在spider中将item_load到pipeline中:
from scrapy.loader import ItemLoader
from myproject.items import Product
l = ItemLoader(item=Product(), response=response)
l.add_value('name', response.xpath('...'))
l.add_value('price', response.xpath('...'))
l.add_value('stock', response.xpath('...'))
l.add_value('last_updated', response.xpath('...'))
yield l.load_item()
那么就需要在pipelines.py中实现对这些items的处理的pipeline,对于不同的spider,可以用它们的name来进行区分处理:
class MyPipeline(object):
def open_spider(self, spider):
if spider.name == 'spider1':
self.file = open('file1.txt', 'a+')
elif spider.name == 'spider2':
self.file = open('file2.txt', 'a+')
def process_item(self, item, spider):
line = json.dumps(dict(item)) + '\n'
self.write(line)
return item
def close_spider(self, spider):
self.file.close()
最后在settings.py中注册这个pipeline,让其真正投入生产线:
ITEM_PIPELINES = {
'myproject.pipelines.MyPipeline': 300,
}
这里规定的数字,决定了组件运行的顺序,数字越小,越先执行。
实践相关问题:
可以参考这里
常见的有防止被目标网站禁止(ban):
- 使用user agent池,轮流选择之一来作为user agent。
- 禁止cookies(参考settings.py的COOKIES_ENABLED),有些站点会使用cookies来发现爬虫的轨迹。
- 设置下载延迟(2或更高)。参考DOWNLOAD_DELAY设置。
- 如果可行,使用Google cache来爬取数据,而不是直接访问站点。
- 使用IP池。例如免费的 Tor项目 或付费服务ProxyMesh。
- 使用高度分布式的下载器(downloader)来绕过禁止(ban),您就只需要专注分析处理页面。这样的例子有: Crawlera
其中,设置随机请求头的问题,这个问题非常建议参考博文:一行代码搞定Scrapy的随机UserAgent
简而言之,只需两步:
- pip安装模块
pip install scrapy-fake-useragent
- 在settings.py加入这个模块,让它进行UserAgent的自动设置。
DOWNLOADER_MIDDLEWARES = {
# 关闭默认的UA方法,并开启自己的UA方法
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,
}