基本配置与命令
1.安装
win系统下有5个步骤
pip3 install wheel twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted pip3 install Twisted 17.1.0 cp35 cp35m win_amd64.wheel pip install pywin32 pip install scrapy
2.创建
scrapy startproject 项目名
- 创建普通爬虫文件
cd 项目名 (进入项目目录之后再创建爬虫文件)
scrapy genspider 爬虫文件名 www.xxx.com
- 创建crawlspider的爬虫文件
scrapy genspider -t crawl 爬虫文件名 www.xxx.com
- 一般注释掉 allowed_domains = ['www.xxx.com']
3.配置项目
ROBOTSTXT_OBEY = False 不遵守机器人规则 USER_AGENT = '' 配置UA LOG_LEVEL = 'ERROR' 配置日志级别
中间件配置,管道配置时将注释取消即可
4.执行项目
scrapy crawl 爬虫文件名 scrapy crawl 爬虫文件名 --nolog 不显示日志运行 scrapy crawl 爬虫文件名 -o 文件名.csv/json 保存结果到文件
应用
1.持久化本地存储
1.使用命令,保存的本地文件格式限制为 (json csv xml jl jsonlines marshal pickle)
scrapy crawl 爬虫文件名 -o 本地文件名.csv
2.使用管道
- 在settings.py中开启管道!!!
- items.py文件中添加字段,如 name = scrapy.Field()
- 注意在爬虫文件中要yield item 管道类中才能接受到item
- 在piplines.py文件中的管道类中的process_item函数中处理
- 爬虫类
-
-
-
- open_spider()
- close_spider()
- process_item()
-
-
2.爬取全部网站的数据
手动请求发送,在爬虫文件中的parse中,(callback是回调另一个parse解析函数)
yield scrapy.Request(url,callback)
3.进行post请求发送
spider类中重写start_requests(self)方法,yield scrapy.FormRequest(url,parse,formdata)
def start_requestS(self): for url in self.start_urls: data = { 'kw':'cat'} yield scrapy.FormRequest(url=url,callback=self.parse,formdata=data)
4.处理cookie
- scrapy默认情况下自动处理cookie,在settings.py中配置
COOKIES_ENABLED = False
5.请求传参
- 使用场景:如果使用scrapy爬取的数据没有在同一张页面中,则必须使用请求传参
- 编码流程:
- 需求:爬取的是 首页中电影的名称和 详情页中电影的简介(全站数据爬取)
- 基于起始url进行数据解析(parse)
- 解析数据
-
-
- 电影的名称
- 详情页的url
- 对详情页的url发起手动请求(指定的回调函数parse_detail),进行请求传参(meta)meta传递给parse_detail这个回调函数
-
- 封装一个其他页码对应url的一个通用的URL模板
- 在for循环外部,手动对其他页的url进行手动请求发送(需要指定回调函数==》parse)
- 定义parse_detail回调方法,在其内部对电影的简介进行解析。解析完毕后,需要将解析到的电影名称和电影的简介封装到同一个item中。
- 接收传递过来的item,并且将解析到的数据存储到item中,将item提交给管道
def parse_news(self,response): item = WangyiproItem() item['title'] = title yield scrapy.Request(url=news_detail_url,callback=self.parse_detail,meta={'item':item}) def parse_detail(self,response): item = response.meta['item'] item['content'] = content yield item
5.中间件
- 在settings中要取消注释才能生效
- DOWNLOADER_MIDDLEWARES 下载中间件: 批量拦截所有的请求和响应
- process_request 拦截请求
- 用来进行UA伪装,结合UA列表使用
- 使用代理ip,结合ip池使用
- process_response 拦截相应
可以在爬取动态加载的页面时结合selenium使用
from scrapy import signals import random #批量拦截所有的请求和响应 class MiddlewearproDownloaderMiddleware(object): #UA池 user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 " "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24" ] #代理池 PROXY_http = [ '153.180.102.104:80', '195.208.131.189:56055', ] PROXY_https = [ '120.83.49.90:9000', '95.189.112.214:35508', ] #拦截正常请求:request就是该方法拦截到的请求,spider就是爬虫类实例化的一个对象 def process_request(self, request, spider): print('this is process_request!!!') #UA伪装 request.headers['User-Agent'] = random.choice(self.user_agent_list) return None #拦截所有的响应 def process_response(self, request, response, spider): return response #拦截发生异常的请求对象 def process_exception(self, request, exception, spider): print('this is process_exception!!!!') #代理ip的设定 if request.url.split(':')[0] == 'http': request.meta['proxy'] = random.choice(self.PROXY_http) else: request.meta['proxy'] = random.choice(self.PROXY_https) #将修正后的请求对象重新进行请求发送 return request
6.scrapy中使用selenium
1.spider类中定义一个属性 (类属性)
browser = Chrome(executable_path='chromedriver.exe')
2.spider类中重写一个方法closed, 在该方法中关闭browser
def closed(self,spider): self.browser.quit()
3.在中间件类的process_response中编写selenium自动化的相关操作
class DownloaderMiddleware(object): #拦截整个工程中所有的响应对象 def process_response(self, request, response, spider): if request.url in spider.urls: #获取了在爬虫类中定义好的浏览器对象 bro = spider.bro bro.get(request.url) bro.execute_script('window.scrollTo(0,document.body.scrollHeight)') sleep(1) #获取携带了新闻数据的页面源码数据 page_text = bro.page_source #实例化一个新的响应对象 new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request) return new_response else: return response
7.CrawlSpider 自动爬取全站的数据
- 作用:就是用于进行全站数据的爬取
- 新建一个CrawlSpider类的爬虫文件
scrapy genspider -t crawl xxx www.xxx.com
- LinkExtractor 连接提取器:根据指定规则(正则表达式)进行连接的提取
- Rule规则解析器:
将链接提取器提取到的链接进行请求发送,然后对获取的页面数据进行指定规则的解析(callback)
一个连接提取器对应唯一一个规则解析器
- 示例 使用CrawlSpider爬取阳光问政王标题及详情页内容
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from sunLinePro.items import SunlineproItem,ContentItem class SunSpider(CrawlSpider): name = 'sun' # allowed_domains = ['www.xxx.com'] start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page='] link= LinkExtractor(allow=r'type=4&page=d+')#提取页码连接 link1 = LinkExtractor(allow=r'question/2019d+/d+.shtml')#提取详情页的链接 rules = ( Rule(link, callback='parse_item', follow=False), Rule(link1, callback='parse_detail'), ) #解析出标题和网友名称 def parse_item(self, response): tr_list = response.xpath('//*[@id="morelist"]/div/table[2]//tr/td/table//tr') for tr in tr_list: title = tr.xpath('./td[2]/a[2]/text()').extract_first() netFriend = tr.xpath('./td[4]/text()').extract_first() item = SunlineproItem() item['title'] = title item['netFriend'] = netFriend yield item #解析出新闻的内容 def parse_detail(self,response): content = response.xpath('/html/body/div[9]/table[2]//tr[1]/td/div[2]//text()').extract() content = ''.join(content) item = ContentItem() item['content'] = content yield item
scrapy分布式爬虫
1.概念:
可以将一组程序执行在多台机器上(分布式机群),使其进行数据的分布爬取。
2.原生的scrapy框架是否可以实现分布式?不可以.
- 调度器不可以被分布式机群共享
- 管道不可以被分布式机群共享
3.借助scrapy-redis这个模块帮助scrapy实现分布式
- scrapy-redis作用:
- 可以提供可以被共享的管道和调度器
4.使用scrapy-redis:
1.安装scrapy-redis:
pip install scrapy-redis
2.爬虫文件中:
- spider对应的分布式类
from scrapy_redis.spiders import RedisSpider
- Crawlspider对应的分布式类
from scrapy_redis.spiders import RedisCrawlSpider
3.修改爬虫文件的代码:
- 将当前爬虫类的父类从CrawlSpider修改成RedisCrawlSpider
- 将start_urls删除
- 添加一个新的类属性redis_key = 'ts' (可以被共享的调度器中的队列名称)
4.设置管道piplines.py
ITEM_PIPELINES = {
'scrapy_redis_ep.pipelines.RedisPipeline': 400
# 'scrapyRedisPro.pipelines.ScrapyredisproPipeline': 300,
}
5.settings.py中设置调度器
# 使用scrapy-redis组件的去重队列,增加了一个去重容器类的配置, 作用使用Redis的set集合来存储请求的指纹数据, 从而实现请求去重的持久化 DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 使用scrapy-redis组件的调度器 SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 是否暂停,配置调度器是否要持久化, 也就是当爬虫结束了, 要不要清空Redis中请求队列和去重指纹的set。如果是True, 就表示要持久化存储, 就不清空数据, 否则清空数据 SCHEDULER_PERSIST = True
6.settings.py中设置redis服务器
REDIS_HOST = '192.168.12.154' REDIS_PORT = 6379 # REDIS_ENCODING = 'utf-8' # REDIS_PARAMS = {'password':'123456'}
7.配置redis:
修改Redis的配置文件:redis.windows.conf
- #bind 127.0.0.1 - protected-mode no
8.启动redis
携带配置文件启动redis服务
redis-server ./redis.windows.conf
启动redis客户端
redis-cli
执行工程
scrapy runspider xxx.py
手动将起始url扔入调度器的队列中,在redis客户端中输入命令(redis-cli), xxx.com为起始url
lpush ts www.xxx.com
查看爬取的所有items
lrange 爬虫文件名:items 0 -1
增量式爬虫
增量式爬虫
- 概念:监测网上数据更新的情况
比如爬取电影网时,过一段时间又会有新的电影产生,这是就不能再将所有数据存储到数据库中了因为会重复
- 数据指纹
将爬取的数据进行hash等方式转换为一个不重复的值,存到数据库中.
比如爬取糗百时,将爬到的标题和段子内容原始数据第一步先持久化存储,第二步再将标题和内容拼接成一个字符串再进行hash生成一个data_id存入数据库, 在下一次爬取时就可以将标题和内容拼接生成hash值(data_id),在数据库中查询是否存在这个id值,进而判断数据是否存在
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from redis import Redis from moviePro.items import MovieproItem class MovieSpider(CrawlSpider): name = 'movie' # allowed_domains = ['www.xxx.com'] start_urls = ['https://www.4567tv.tv/frim/index1.html'] link = LinkExtractor(allow=r'/frim/index1-d+.html') rules = ( Rule(link, callback='parse_item', follow=False), ) conn = Redis(host='127.0.0.1',port=6379) #解析电影的名称和详情页的url def parse_item(self, response): li_list = response.xpath('/html/body/div[1]/div/div/div/div[2]/ul/li') for li in li_list: title = li.xpath('./div/a/@title').extract_first() detail_url = 'https://www.4567tv.tv'+li.xpath('./div/a/@href').extract_first() item = MovieproItem() item['title'] = title #判断该详情页的url是否进行请求发送 ex = self.conn.sadd('movie_detail_urls',detail_url) if ex == 1:#说明detail_url不存在于redis的set中 print('已有最新数据更新,请爬......') yield scrapy.Request(url=detail_url,callback=self.parse_detail,meta={'item':item}) else: print('暂无新数据的更新!!!') def parse_detail(self,response): item = response.meta['item'] desc = response.xpath('/html/body/div[1]/div/div/div/div[2]/p[5]/span[2]/text()').extract_first() item['desc'] = desc yield item
管道文件:
class MovieproPipeline(object): def process_item(self, item, spider): dic = { 'title':item['title'], 'desc':item['desc'] } conn = spider.conn conn.lpush('movie_data',dic) return item