Scrapy爬取知名技术文章网站
scrapy安装以及目录结构介绍
创建有python3的虚拟环境
mkvirtualenv mkvirtualenv py3env
安装scrapy
进入虚拟环境py3env,把pip的源设置为豆瓣源。这个命令执行完毕后,以后使用pip安装Python包时就会从豆瓣源下载,速度会更快
pip config set global.index-url https://pypi.doubanio.com/simple
安装scrapy依赖包lxml、twisted
安装scrapy
pip install -i https://pypi.douban.com/simple/ scrapy
补充
进入虚拟环境: workon py3env
创建项目: scrapy startproject ArticleSpider
建立spider: scrapy genspider jobbole blog.jobbple.com
PyCharm 调试scrapy 执行流程
scrapy目录结构:
scrapy.cfg
:配置文件setings.py
:基本设置SPIDER_MODULES = ['ArticleSpider.spiders'] #存放spider的路径
NEWSPIDER_MODULE = 'ArticleSpider.spiders'- pipelines.py:做跟数据存储相关的东西
- middilewares.py:自己定义的middlewares 定义方法,处理响应的IO操作
- init.py:项目的初始化文件
- items.py:定义我们所要爬取的信息的相关属性。Item对象是种类似于表单,用来保存获取到的数据
此时,pycharm下articlespiders项目还没完全配置好:
①需要在pycharm的setting中的project interpreter下配置python:使用虚拟环境articlespider下的python3.6
②爬虫项目不像Django项目,没有自动配置好调试或运行相关,需要我们手动生成一个main.py文件,作为启动文件:
在PyCharm中运行爬虫文件
在ArticleSpider文件根目录下建立main.py文件
要学会用断点和DEBUG
在实战中操作
可能出现的错误
File "C:\Users\用户名\AppData\Local\Programs\Python\Python39\lib\site-packages\cryptography\exceptions.py", line 9, in <module>
from cryptography.hazmat.bindings._rust import exceptions as rust_exceptions
ImportError: DLL load failed while importing _rust: 找不到指定的程序。解决方法:尝试过网上一系列解决方法,没有解决。所使用版本为Python3.9.0,换个版本问题解决。。。
xpath的用法
xpath简介
- xpath使用路径表达式在xml和html中进行导航。
- xpath包含标准函数库。
- xpath是一个w3c的标准。
- xpath速度要远远超beautifulsoup
xpath节点关系
- 父节点
- 子节点
- 同胞节点
- 先辈节点
- 后代节点
xpath语法
表达式 |
说明 |
article |
选取所有article元素的所有子节点 |
/article |
选取根元素article |
article/a |
选取所有属于article的子元素的a元素 |
//div |
选取所有div子元素(不论出现在文档任何地方) |
article//div |
选取所有属于article元素的后代的div元素,不管它出现在article之下的任何位置 |
//@class |
选取所有名为class的属性 |
/article/div[1] |
选取属于article子元素的第一个div元素 |
/article/div[last()] |
选取属于article子元素的最后一个div元素 |
/article/div[last()-1] |
选取属于article子元素的倒数第二个div元素 |
//div[@lang] |
选取所有拥有lang属性的div元素 |
//div[@lang='eng'] |
选取所有lang属性为eng的div元素 |
/div/* |
选取属于div元素的所有子节点 |
//* |
选取所有元素 |
//div[@*] |
选取所有带属性的div元素 |
//div/a | //div/p |
选取所有div元素的a和p元素 |
//span | //ul |
选取文档中的span和ul元素 |
article/div/p | //span |
选取所有属于article元素的div元素的p元素 以及 文档中所有的span元素 |
contains()用法
response.xpath("//span[contains(@class, 'bookmark-btn')]/text()").extract()[0]
表示在span标签中class属性中含有 bookmark-btn 即为符合
正文保留html标签,以便后续研究
scrapy shell url 调试xpath
如果在py3中就都显示中文了
re.math(reg,html).group() #正则匹配
tag_list=['职场','2 评论','今昔'] [element for element in tag_list if not element.strip().endswith('评论')] #结果['职场', '今昔']
xpath提取元素
css选择器实现字段解析
编写spider完成抓取过程
parse.urljoin(url,post_url)的使用
实现模拟登陆(undetected_chromedriver)
上处代码使用undetected_chromedriver时会自动识别Chrome浏览器版本并自动下载对应的Chromedriver,运行时总是下载失败一直未解决,为了后续项目进行,采用selenium模拟登录,登录时会被cnblog反爬识别,采用qq登录即可
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://account.cnblogs.com/signin")
完成页面爬取
提取详情页信息
item的使用
数据爬取的任务就是从非结构的数据中提取出结构性的数据。
items 可以让我们自定义自己的字段(类似于字典,但比字典的功能更齐全)
当我们爬取数据,获 取数据后需要将数据传给下个函数调用,则可以利用scrapy request下的meta属性,meta接收的是字典类型,当使用request爬取数据时,将需要传递给下个函数的数据放到meta中,meta中的数据会传给下个函数的response中去,可以被下个函数使用
关于scrapy request的meta:
Request中meta参数的作用是传递信息给下一个函数,使用过程可以理解成:
把需要传递的信息赋值给这个叫meta的变量,
但meta只接受字典类型的赋值,因此
要把待传递的信息改成“字典”的形式,即:
meta={'key1':value1,'key2':value2}
如果想在下一个函数中取出value1,
只需得到上一个函数的meta['key1']即可,
因为meta是随着Request产生时传递的,
下一个函数得到的Response对象中就会有meta,
即response.meta,
meta是一个dict,主要是用解析函数之间传递值,一种常见的情况:在parse中给item某些字段提取了值,但是另外一些值需要在parse_item中提取,这时候需要将parse中的item传到parse_item方法中处理,显然无法直接给parse_item设置而外参数。 Request对象接受一个meta参数,一个字典对象,同时Response对象有一个meta属性可以取到相应request传过来的meta
request meta
request meta
关于urllib parse.urljoin(url1,url2):
parse.url:能将相对路径,自动补充域名补全路径,前提主域名response能获取域名路径 一般爬取网上数据时,存在网页某些url是相对路径形式展示的,通过js代码等操作补全,此时我们要爬取完整url路径,就需要用到parse.urljoin方法了 如果你是相对路径,没有补全域名,我就从response里取出来补全,如果你有域名我则不起作用
items.py创建类
在jobbole.py parse_detail 中实例化 JobBoleArticleItem , 并将爬取到的数据放入 JobBoleArticleItem 对象中:
setting的相关设置
robot协议:目标网站禁止爬取的连接目录
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
pipelines管道
ITEM_PIPELINES:item的管道,配置好ArticlespiderPipeline路径,当执行代码 yield article_item 时,程序会转到pipelines.py文件中执行 ArticlespiderPipeline 类,
也就是通过 yield article_item 将article_item数据传给pipelines调用。并执行pipelines中的相关类,如: ArticlespiderPipeline ,在 ArticlespiderPipeline 类中,我们可以通过代码编写,将数据存到数据库中去
# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
"ArticleSpider.pipelines.ArticlespiderPipeline": 300,
}
ITEM_PIPELINES
pipelines.py文件代码
class ArticlespiderPipeline:
def process_item(self, item, spider):
return item
pipelines.py
scrapy配置图片下载
配置setting.py
配置pipeline
ITEM_PIPELINES = { "ArticleSpider.pipelines.ArticlespiderPipeline": 300, "scrapy.pipelines.images.ImagesPipeline": 1 }
配置images文件夹的路径:
Scrapy中的pipelines提供了默认的文件、图片、媒体等下载保存方式,如上所述,将路径配置上去就会相应的执行scrapy/pipelines的相关函数 ,
另外,ITEM_PIPELINES是一个数据管道的登记表,每一项后面的具体数字代表它的优先级,数字越小,越早进入管道执行
接下来我们新建一个images文件夹用于保存图片,在setting中配置images文件夹的路径:
import sys
import os
project_dir = os.path.dirname(os.path.abspath(__file__))
IMAGES_STORE = os.path.join(project_dir, "images"):
指定某字段(我们获取图片路径的字段)为我们的图片文件处理
IMAGES_URLS_FIELD = "front_image_url" # 指定爬取的某(图片)字段作为图片处理
若报错ModuleNotFoundError:No module named 'PIL',是因为图片保存等需要pillow库来操作,解决方法在虚拟环境中安装pillow:pip install pillow
若报错ValueError:Missing scheme in request url:h
原因是'scrapy.pipelines.images.ImagesPipeline' 中对图片的要求是数组格式,而我们目前传递的图片类型(front_image_url)是字符串格式,因此我们需要将爬取的图片类型改成数组类型获取
article_item["front_image_url"] = front_image_url
# ↓改成数组形式
article_item["front_image_url"] = [front_image_url]
额外知识:
scrapy下的pipelines目录下有files.py、images.py、media.py ,用于处理文件、图片、用户上传下载媒体等数据
IMAGES_URLS_FIELD = "front_image_url" # 指定该字段作为图片处理对象 IMAGES_STORE = os.path.join(project_dir, 'images') # 图片存放路径 IMAGES_MIN_HEIGHT = 100 # 图片最小高度
IMAGES_MIN_WIDTH = 100 # 图片最小宽度使用scrapy自带的image.py处理图片,只需要按要求命名一致,以及在ITEM_PIPELINES 配置好所使用的类即可:ImagesPipeline ,
使用files.py、media.py也是一样的道理。
数据保存相关
保存到本地json文件中
方式一:
在pipelines.py文件中新建类:JsonWithEncodingPipeline
打开文件时不直接使用 open ,用python自带的codecs包,可以避免一些编码方面的问题出现
使用json.dumps时,不使用ensure_ascii=False ,默认输出中文是ASCII字符码,要想正确输出中文,需要指定ensure_ascii=False
pipelines.py/JsonWithEncodingPipelines:
class JsonWithEncodingPipeline(object):
# 自定义json文件的导出
def __init__(self):
self.file = codecs.open('article.json', 'w', 'utf-8')
def process_item(self, item, spider):
# 将数据转换为字符串
lines = json.dumps(dict(item), ensure_ascii=False) + "\n"
self.file.write(lines)
return item
def spider_closed(self, spider):
self.file.close()
在setting中配置ITEM_PIPELINES:
ITEM_PIPELINES = {
'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,
}
当爬虫爬取数据,保存到 item 中,经过yield item ,转到pipelines中执行里面的类
方式二:
使用scrapy中自带的类保存文件:
# scrapy/exporters ['BaseItemExporter', 'PprintItemExporter', 'PickleItemExporter','CsvItemExporter', 'XmlItemExporter','JsonLinesItemExporter','JsonItemExporter', # 保存json文件 'MarshalItemExporter']
from scrapy.exporters import JsonItemExporter class JsonExporterPipeline(object):
#调用scrapy提供的json export导出json文件
def __init__(self):
self.file = open('articleexport.json', 'wb')
self.exporter = JsonItemExporter(self.file, encoding="utf-8", ensure_ascii=False)
self.exporter.start_exporting() def process_item(self, item, spider):
self.exporter.export_item(item)
return item def spider_closed(self, spider):
self.exporter.finish_exporting()
self.file.close()
在setting中配置ITEM_PIPELINES:
ITEM_PIPELINES = {
"ArticleSpider.pipelines.JsonExporterPipeline": 3,
}
将数据保存到mysql中
在虚拟环境下安装:mysqlclient
pip install mysqlclient
开始我们的数据保存到mysql操作
方式一: 利用pipelines保存数据到数据库(同步)
import MySQLdb
class MysqlPipeline(object):
# 数据保存
def __init__(self):
"""
初始化构造函数,连接数据库
"""
self.conn = MySQLdb.connect("127.0.0.1", "root", "123456", "article_spider", charset="utf8", use_unicode=True)
self.cursor = self.conn.cursor()# 创建游标 def process_item(self, item, spider):
"""
处理数据
"""
insert_sql = """
insert into jobbole_article(title, url, url_object_id, front_image_url, front_image_path, parise_nums, comment_nums, fav_nums, tags, content, create_date)
values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
params = list()
params.append(item.get("title", ""))
params.append(item.get("url", ""))
params.append(item.get("url_object_id", ""))
# 将list[]转换成字符串
front_image = ",".join(item.get("front_image_url", []))
params.append(item.get(front_image))
params.append(item.get("front_image_path", ""))
params.append(item.get("parise_nums", 0))
params.append(item.get("content_nums", 0))
params.append(item.get("fav_nums", 0))
params.append(item.get("tags", ""))
params.append(item.get("content", ""))
params.append(item.get("create_date", "1970-01-01"))
self.cursor.execute(insert_sql, (params))
self.conn.commit() return item
在setting中配置ITEM_PIPELINES:
ITEM_PIPELINES = {
"ArticleSpider.pipelines.MysqlPipeline": 4,
}
方式二:利用pipelines保存数据到数据库(twisted异步)
因为我们的爬取速度可能大于数据库存储的速度,最好是异步操作(异步化操作mysql)
在setting.py中配置数据库连接的相关配置参数:
MYSQL_HOST = "127.0.0.1"
MYSQL_DBNAME = "article_spider"
MYSQL_USER = "root"
MYSQL_PASSWORD = "123456"
在pipelines.py中新建类:MysqlTwistedPipline 执行顺序:from_settings → init → pross_item
import MySQLdb
import MySQLdb.cursors
from twisted.enterprise import adbapi
class MySQLTwistedPipeline(object):
def __init__(self, dbpool):
self.dbpool = dbpool # 重载方法
@classmethod
def from_settings(cls, settings):
dbparms = dict(
host=settings["MYSQL_HOST"],
db=settings["MYSQL_DBNAME"],
user=settings["MYSQL_USER"],
passwd=settings["MYSQL_PASSWORD"],
charset="utf8",
cursorclass=MySQLdb.cursors.DictCursor,
use_unicode=True,
)
# 连接池
dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)
return cls(dbpool) def process_item(self, item, spider):
# 将某个方法扔到池里运行
query = self.dbpool.runInteraction(self.do_insert,item)
# 报错的处理方法
query.addErrback(self.handle_error, item, spider) def handle_error(self, failure, item, spider):
print(failure)
def do_insert(self, cursor, item):
"""
入库逻辑
"""
insert_sql = """
insert into jobbole_article(title, url, url_object_id, front_image_url, front_image_path, parise_nums, comment_nums, fav_nums, tags, content, create_date)
values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
params = list()
params.append(item.get("title", ""))
params.append(item.get("url", ""))
params.append(item.get("url_object_id", ""))
# 将list[]转换成字符串
front_image = ",".join(item.get("front_image_url", []))
params.append(item.get(front_image))
params.append(item.get("front_image_path", ""))
params.append(item.get("parise_nums", 0))
params.append(item.get("content_nums", 0))
params.append(item.get("fav_nums", 0))
params.append(item.get("tags", ""))
params.append(item.get("content", ""))
params.append(item.get("create_date", "1970-01-01")) cursor.execute(insert_sql, tuple(params))
在setting中配置ITEM_PIPELINES:
ITEM_PIPELINES = {
"ArticleSpider.pipelines.MysqlTwistedPipeline": 4,
}
运行后出错AttributeError: module 'MySQLdb' has no attribute 'cursors'
解决方法:修改from_setting方法
@classmethod
def from_settings(cls, settings):
from MySQLdb.cursors import DictCursor
dbparms = dict(
host=settings["MYSQL_HOST"],
db=settings["MYSQL_DBNAME"],
user=settings["MYSQL_USER"],
passwd=settings["MYSQL_PASSWORD"],
charset="utf8",
cursorclass=DictCursor,
use_unicode=True,
)
# 连接池
dbpool = adbapi.ConnectionPool("MySQLdb", **dbparms)
return cls(dbpool)
问题:数据插入时遇到主键冲突问题
解决方法:插入值的时候更新字段
values (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE parise_nums = VALUES(parise_nums)
scrapy提供item loader机制提取信息
# 在parse_detail方法中配置
item_loader = ItemLoader(item=JobBoleArticleItem(), response=response)
item_loader.add_css("title", "#news_title a::text")
item_loader.add_css("create_date", "#news_info .time::text")
item_loader.add_css("content", "#news_content")
item_loader.add_css("tags", "news_tags a::text")
item_loader.add_value("url", response.url)
item_loader.add_value("front_image_url", response.meta.get('front_image_url', ""))
# 加载item
article_item = item_loader.load_item()
MapCompose,TakeFirst的使用
Scrapy爬取知名技术文章网站的更多相关文章
- Python3.6+Scrapy爬取知名技术文章网站
爬取分析 伯乐在线已经提供了所有文章的接口,还有下一页的接口,所有我们可以直接爬取一页,再翻页爬. 环境搭建 Windows下安装Python: http://www.cnblogs.com/0bug ...
- 第4章 scrapy爬取知名技术文章网站(2)
4-8~9 编写spider爬取jobbole的所有文章 # -*- coding: utf-8 -*- import re import scrapy import datetime from sc ...
- 第4章 scrapy爬取知名技术文章网站(1)
4-1 scrapy安装以及目录结构介绍 安装scrapy可以看我另外一篇博文:Scrapy的安装--------Windows.linux.mac等操作平台,现在是在虚拟环境中安装可能有不同. 1. ...
- 用scrapy爬取亚马逊网站项目
这次爬取亚马逊网站,用到了scrapy,代理池,和中间件: spiders里面: # -*- coding: utf-8 -*- import scrapy from scrapy.http.requ ...
- 使用scrapy爬取jian shu文章
settings.py中一些东西的含义可以看一下这里 python的scrapy框架的使用 和xpath的使用 && scrapy中request和response的函数参数 & ...
- 爬虫框架之Scrapy——爬取某招聘信息网站
案例1:爬取内容存储为一个文件 1.建立项目 C:\pythonStudy\ScrapyProject>scrapy startproject tenCent New Scrapy projec ...
- 第5章 scrapy爬取知名问答网站
第五章感觉是第四章的练习项目,无非就是多了一个模拟登录. 不分小节记录了,直接上知识点,可能比较乱. 1.常见的httpcode: 2.怎么找post参数? 先找到登录的页面,打开firebug,输入 ...
- Scrapy爬取伯乐在线文章
首先搭建虚拟环境,创建工程 scrapy startproject ArticleSpider cd ArticleSpider scrapy genspider jobbole blog.jobbo ...
- scrapy爬取伯乐在线文章数据
创建项目 切换到ArticleSpider目录下创建爬虫文件 设置settings.py爬虫协议为False 编写启动爬虫文件main.py
- 一文搞定scrapy爬取众多知名技术博客文章保存到本地数据库,包含:cnblog、csdn、51cto、itpub、jobbole、oschina等
本文旨在通过爬取一系列博客网站技术文章的实践,介绍一下scrapy这个python语言中强大的整站爬虫框架的使用.各位童鞋可不要用来干坏事哦,这些技术博客平台也是为了让我们大家更方便的交流.学习.提高 ...
随机推荐
- [FE] G2Plot 在 Vue 中使用 CDN 方式避免构建时增大 js 体积
使用 npm.yarn 方式安装的包,虽方便使用,但是会极大增加 vendor.xx.js 体积,拖慢网站运行速度. 以 G2Plot 为例,实际在 build 构建时,会下载一些额外字体到 vend ...
- 我第一个开源AI小产品-video2blog即将正式发布
前言 首先它是为了解决我自己的个人问题.不管能不能帮到你,或者对于看到的你是否有点利用价值,也没太大的关系,最起码你可以来看看我开发小产品的整个过程. 一段时间以来,我开始通过youtube平台来获取 ...
- Phpstrom开发工具Sftp的使用
- WEB服务与NGINX(12)-NGINX的变量
目录 1. nginx的变量 1.1 内置变量 1.2 自定义变量 1. nginx的变量 nginx的变量可以在配置文件中引用,作为功能判断或日志等场景使用,变量可以分为内置变量和自定义变量. 内置 ...
- Git基本操作命令大全
一.全局配置命令 ## 配置级别: –local(默认,高级优先):只影响本地仓库 –global(中优先级):只影响所有当前用户的git仓库 –system(低优先级):影响到全系统的git仓库 # ...
- spring-boot集成Quartz-job存储方式二RAM
简单区分: RAM:程序启动从数据库中读取原始job配置(也可以从配置文件中读取),job中间运行过程在RAM内存中,程序停止或重启后,RAM中数据丢失,再次启动的时候会重新读取job配置.适合于单机 ...
- CSS布局概念与技术教程
以下是一份CSS布局学习大纲,它涵盖了基本到高级的CSS布局概念和技术 引言 欢迎来到CSS教程!如果你已经掌握了HTML的基础知识,那么你即将进入一个全新的世界,通过学习CSS(Cascading ...
- 4G 信令中的 PCO 字段
目录 文章目录 目录 Protocol Configuration Option Protocol Configuration Option PCO(Protocol Configuration Op ...
- 2024-05-15:用go语言,考虑一个整数 k 和一个整数 x。 对于一个数字 num, 在其二进制表示中, 从最低有效位开始, 我们计算在 x,2x,3x 等位置处设定位的数量来确定其价值。
2024-05-15:用go语言,考虑一个整数 k 和一个整数 x. 对于一个数字 num, 在其二进制表示中, 从最低有效位开始, 我们计算在 x,2x,3x 等位置处设定位的数量来确定其价值. 举 ...
- 在 JS 中调整 canvas 里的文字间距
实现说明: 在 JS 中 canvas 原生没有支持对文字间距的调整,我们可以通过将文字的每个字符单独渲染来实现.本案例从 CanvasRenderingContext2D 对象的原型链上扩展了一个用 ...