本系列文档用于对Python爬虫技术的学习进行简单的教程讲解,巩固自己技术知识的同时,万一一不小心又正好对你有用那就更好了。
Python 版本是3.7.4
上篇文章我们讲述了下载器中间件的概念,以及如何使用下载器中间件如何使用下载器中间件进行动态随机设置请求头和设置代理IP的方法。这一篇文章我们就讲述一个少高级一点的中间件用法,那就是Scrapy+selenium+chromedriver进行结合使用的方法。
为什么我们要继续这种结合使用呢?这是由于目前的网站很多都是使用动态加载
生成页面,我们在直接访问链接的时候获取不到页面上的信息(去分析其ajax请求接口规则又太过复杂),所以我们使用这一方案进行爬取页面信息。
通过我们之前对Scrapy的学习我们知道Scrapy
框架的架构可以分成五大模块(spider
、engine
、downloader
、scheduler
、item pipelines
)+两个中间件(spider中间件
、downloader中间件
)。以及Scrapy的执行流程(为了方便查看,在这里在展示一遍):
并且通过前面一章我们学习了中间件的用法。那么使用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,
}
将数据库相关表设计好,运行代码即可。