综述
本系列文档用于对Python爬虫技术的学习进行简单的教程讲解,巩固自己技术知识的同时,万一一不小心又正好对你有用那就更好了。
Python 版本是3.7.4
上篇文章我们讲述了下载器中间件的概念,以及如何使用下载器中间件如何使用下载器中间件进行动态随机设置请求头和设置代理IP的方法。这一篇文章我们就讲述一个少高级一点的中间件用法,那就是Scrapy+selenium+chromedriver进行结合使用的方法。
为什么我们要继续这种结合使用呢?这是由于目前的网站很多都是使用动态加载
生成页面,我们在直接访问链接的时候获取不到页面上的信息(去分析其ajax请求接口规则又太过复杂),所以我们使用这一方案进行爬取页面信息。
原理讲解
通过我们之前对Scrapy的学习我们知道Scrapy
框架的架构可以分成五大模块(spider
、engine
、downloader
、scheduler
、item pipelines
)+两个中间件(spider中间件
、downloader中间件
)。以及Scrapy的执行流程(为了方便查看,在这里在展示一遍):
- 引擎从Spiders中获取到最初的要爬取的请求(Requests);
- 引擎安排请求(Requests)到调度器中,并向调度器请求下一个要爬取的请求(Requests);
- 调度器返回下一个要爬取的请求(Requests)给引擎;
- 引擎将上步中得到的请求(Requests)通过下载器中间件(Downloader Middlewares)发送给下载器(Downloader ),这个过程中下载器中间件(Downloader Middlewares)中的process_request()函数会被调用到;
- 一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(Downloader Middlewares)发送给引擎,这个过程中下载器中间件(Downloader Middlewares)中的process_response()函数会被调用到;
- 引擎从下载器中得到上步中的Response并通过Spider中间件(Spider Middlewares)发送给Spider处理,这个过程中Spider中间件(Spider Middlewares)中的process_spider_input()函数会被调用到;
- Spider处理Response并通过Spider中间件(Spider Middlewares)返回爬取到的Item及(跟进的)新的Request给引擎,这个过程中Spider中间件(Spider Middlewares)的process_spider_output()函数会被调用到;
- 引擎将上步中Spider处理的其爬取到的Item给Item 管道(Pipeline),将Spider处理的Request发送给调度器,并向调度器请求可能存在的下一个要爬取的请求(Requests);
- (从第二步)重复直到调度器中没有更多的请求(Requests)。
并且通过前面一章我们学习了中间件的用法。那么使用Scrapy+selenium+chromedriver进行结合开发的原理就是将selenium+chromedriver写到中间件中,在执行到第四步的时候直接在process_request()
返回response
对象给引擎,就不会再去下载器中进行请求访问(也就是我们没有使用scrapy框架的downloader模块,直接在下载器中间件中完成了请求,并且返回了请求结果resopnse
给引擎)。
开发实例
下面我们就已爬取简书网数据为例进行实例开发。
- 使用
scrapy startproject jianshu_spider
命令创建项目; - 使用
scrapy genspider -t crawl jianshu jianshu.com
创建爬虫; - 开发
spider
目录下爬虫文件jianshu.py
代码如下:from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from jianshu_spider.items import JianshuSpiderItem class JianshuSpider(CrawlSpider): name = 'jianshu' allowed_domains = ['jianshu.com'] start_urls = ['https://www.jianshu.com/'] # 分析简书文章链接发现其链接地址结构为:域名 + /p/ + 12为数字字母组合字符串 rules = ( Rule(LinkExtractor(allow=r'.*/p/[0-9a-z]{12}.*'), callback='parse_detail', follow=True), ) def parse_detail(self, response): """ 进行爬取结果分析 :param response: :return: """ title = response.xpath('//h1[@class="title"]/text()').get() avatar = response.xpath('//a[@class="avatar"]/img/@src').get() author = response.xpath('//span[@class="name"]/text()').get() pub_time = response.xpath('//span[@class="publish-time"]/text()').get() url = response.url url1 = url.split('?')[0] article_id = url1.split('/')[-1] content = response.xpath('//div[@class="show-content"]').get() word_count = response.xpath('//span[@class="wordage"]/text()').get() comment_count = response.xpath('//span[@class="comments-count"]/text()').get() like_count = response.xpath('//span[@class="likes-count"]/text()').get() read_count = response.xpath('//span[@class="views-count"]/text()').get() subjects = ','.join(response.xpath('//div[@class="include-collection"]/a/div/text()').getall()) item = JianshuSpiderItem( title=title, avatar=avatar, author=author, pub_time=pub_time, origin_url=url1, article_id=article_id, content=content, word_count=word_count, comment_count=comment_count, like_count=like_count, read_count=read_count, subjects=subjects ) yield item
- 开发
items.py
代码如下:import scrapy class JianshuSpiderItem(scrapy.Item): """ 定义所需字段 """ title = scrapy.Field() content = scrapy.Field() article_id = scrapy.Field() origin_url = scrapy.Field() author = scrapy.Field() avatar = scrapy.Field() pub_time = scrapy.Field() read_count = scrapy.Field() like_count = scrapy.Field() word_count = scrapy.Field() subjects = scrapy.Field() comment_count = scrapy.Field()
- 开发
middlewares.py
中间件代码如下:import time from scrapy.http.response.html import HtmlResponse from selenium import webdriver class SeleniumDownloadMiddleware(object): """ selenium 下载中间件 """ def __init__(self): self.driver = webdriver.Chrome(executable_path=r'E:\Python_Code\s1\chromedriver_win32\chromedriver.exe') def process_request(self, request, spider): self.driver.get(request.url) time.sleep(1) try: while True: show_more = self.driver.find_element_by_class_name('show-more') show_more.click() time.sleep(3) if not show_more: break except: pass # 获得网页源代码 source = self.driver.page_source # 构造response对象 | 进行返回 response = HtmlResponse(url=self.driver.current_url, body=source, request=request, encoding='utf-8') return response
- 开发
piplines.py
代码如下:import pymysql from pymysql import cursors from twisted.enterprise import adbapi # 进行数据处理 class JianshuSpiderPipeline(object): """ 这种方法只能同步进行保存到数据库 """ def __init__(self): db_parames = { 'host': '127.0.0.1', 'port': 3306, 'user': 'root', 'password': 'root', 'database': 'jianshu_spider', 'charset': 'utf8' } self.conn = pymysql.connect(**db_parames) self.cursor = self.conn.cursor() self._sql = None def process_item(self, item, spider): self.cursor.execute(self.sql, ( item['title'], item['content'], item['author'], item['avatar'], item['pub_time'], item['article_id'], item['origin_url'],)) # self.cursor.commit() print(item) return item @property def sql(self): if not self._sql: self._sql = """ INSERT INTO article(id,title,content,author,avatar,pub_time,article_id,origin_url) VALUE(null,%s,%s,%s,%s,%s,%s,%s) """ return self._sql return self._sql class JianshuTwistedPipeline(object): """ 使用Twisted进行异步保存到数据库 """ def __init__(self): db_parames = { 'host': '127.0.0.1', 'port': 3306, 'user': 'root', 'password': 'root', 'database': 'jianshu_spider', 'charset': 'utf8', 'cursorclass': cursors.DictCursor } # 定义数据库连接池 self.dbpool = adbapi.ConnectionPool('pymysql', **db_parames) self._sql = None @property def sql(self): if not self._sql: self._sql = """ INSERT INTO article(id,title,content,author,avatar,pub_time,article_id,origin_url,word_count) VALUE(null,%s,%s,%s,%s,%s,%s,%s,%s) """ return self._sql return self._sql def process_item(self, item, spider): defer = self.dbpool.runInteraction(self.insert_item, item) defer.addErrback(self.handle_error, item, spider) print(item) return item def insert_item(self, cursor, item): cursor.execute(self.sql, ( item['title'], item['content'], item['author'], item['avatar'], item['pub_time'], item['article_id'], item['origin_url'], item['word_count'])) def handle_error(self, error, item, spider): print('=' * 15 + 'error' + '=' * 15) print(error) print('=' * 15 + 'error' + '=' * 15)
- 进行设置
setting.py
文件相关配置更改如下:# 开启item pipelines ITEM_PIPELINES = { 'jianshu_spider.pipelines.JianshuTwistedPipeline': 300, # 'jianshu_spider.pipelines.JianshuSpiderPipeline': 300, } # 开启中间件 DOWNLOADER_MIDDLEWARES = { 'jianshu_spider.middlewares.SeleniumDownloadMiddleware': 543, }
将数据库相关表设计好,运行代码即可。
其他博文链接
- Python爬虫1.1 — urllib基础用法教程
- Python爬虫1.2 — urllib高级用法教程
- Python爬虫1.3 — requests基础用法教程
- Python爬虫1.4 — requests高级用法教程
- Python爬虫2.1 — BeautifulSoup用法教程
- Python爬虫2.2 — xpath用法教程
- Python爬虫3.1 — json用法教程
- Python爬虫3.2 — csv用法教程
- Python爬虫3.3 — txt用法教程
- Python爬虫4.1 — threading(多线程)用法教程
- Python爬虫4.2 — ajax(动态网页数据抓取)用法教程
- Python爬虫4.3 — selenium基础用法教程
- Python爬虫4.4 — selenium高级用法教程
- Python爬虫4.5 — tesseract(图片验证码识别)用法教程
- Python爬虫5.1 — scrapy框架简单入门
- Python爬虫5.2 — scrapy框架pipeline模块的使用
- Python爬虫5.3 — scrapy框架spider[Request和Response]模块的使用
- Python爬虫5.4 — scrapy框架items模块的使用
- Python爬虫5.5 — scrapy框架logging模块的使用
- Python爬虫5.6 — scrapy框架setting模块的使用
- Python爬虫5.7 — scrapy框架Shell命令的使用
- Python爬虫5.8 — scrapy框架CrawlSpider模块的使用
- Python爬虫5.9 — scrapy框架下载文件和图片
- Python爬虫5.10 — scrapy框架Download Middlewares[下载器中间件]的使用