#scrapy 的优势
1 request和beautifulsoup是库,scrapy是框架,不是一个层次的
2 scrapy框架中可以加入request和beautifulsoup,可以基层很多第三方库
3 scrapy基于twisted,实际上是一个异步i/o的框架,所以性能是最大的优势
4 scrapy方便扩展,提供了很多内置功能
5 scrapy内置的css和xpath selector 非常方便,beautifulsoup最大的缺点就是慢。
#网页分类
1 静态网页
事先在服务器端生成好的
2 动态网页
根据传递的参数不同,发送请求获取浏览器
3 webservice(restapi)
#爬虫能做什么
1 搜索引擎—百度 谷歌 垂直搜索引擎
2 推荐引擎–今日头条 根据浏览记录来爬取相关内容推荐给用户
3 机器学习的数据样本
4 数据分析,舆情分析
#正则表达式
!正则表达式是反向匹配,从后往前
!正则是默认贪婪模式,贪婪模式,就是在整个表达式匹配成功的前提下,尽可能多的匹配
!正则表达式会提取括号内的内容
1 "^"
:定义开头字符,以符号后面的字符开头,"^b"
代表以b字母开头
"$"
:定义结尾字符,以符号前面的字符结尾
2 "."
: 任意字符,中英文下划线都可以
3 “*”
:它前面的字符可以出现多次
4 “?”:非贪婪模式,非贪婪模式,就是在整个表达式匹配成功的前提下,尽可能少的匹配
5 “+”
:符号前面的内容至少出现一次,
6 {2} 前面的字符出现的次数
{2,5} 出现最小2次最多5次
{2,}出现2次或者更多
7 “|”:或,要在括号里使用
8“[abcd]”:中括号里面的字符满足一个即可,
可以写区间[0-9] a-z [A-Za-z0-9_]
中括号里面写:^1 表示非1
9 \s 空格
\S 只要不是空格都可以
\w [A-Za-z0-9_]
\W 和\w相反
\d 数字
10 [\u4E00-\u9FA5] 汉字
深度优先和广度优先
深度优先,递归实现,如果递归太深或者递归没有跳出会发生栈溢出
广度优先,队列实现
爬虫去重策略
1 把访问过的url保存到数据库中,但是速度慢
2 把访问过的url保存到set中,也就是内存中,只需要o(1)的代价就可以查询到
url,但是内存占用大
3.url经过md5等方法哈希后保存到set中,可以将字符缩减到固定长度,比如128bit,就是16byte
4.用bitmap方法,将访问过的url通过hash函数映射到某一位,申请一个8个bit,也就是一个byte,将访问过的hash函数映射到某一个位置上
5 bloofilter方法对bitmap进行改进,多重hash函数降低冲突
#xpath 中的css语法
用scrapy创建项目
1.解析文章
2.获取列表和下一页,用Request,传url,来调用解析文章的方法
3. item
在item.py中创建一个item实例,然后在jobbole爬取文章后实例化这个item并填充数据,调用yeild 实例 就会把item传到pipelins中
下载图片并保存到本地
创建一个images文件夹
要把setting.py中的
ITEM_PIPELINES = {
'ArticleSprider.pipelines.ArticlespriderPipeline': 300,
}
打开,改成:
ITEM_PIPELINES = {
'ArticleSprider.pipelines.ArticlespriderPipeline': 300,
'scrapy.pipelines.images.ImagesPipeline': 1 ,# 后面的数字越小,越优先执行
}
IMAGES_URLS_FIELD = "front_img_url" # 这样scrapy会去找到这个front_img_url 然后下载图片
project_dir = os.path.abspath(os.path.dirname(__file__)) # 当前目录的路径
IMAGES_STORE = os.path.join(project_dir,'images') # 配置图片保存路径
记得import os ,配置以后会把front_img_url当成一个数组,所以在填充数据的时候记得以数组填充。这里还需要安装一个东西,在cmd进入虚拟环境`pip install pillow` 用豆瓣云会快一点
获取图片的路径
重写pipelines文件中处理图片的方法,
导入scrapy.pipelines.images包,获取path并填充到item在返回
from scrapy.pipelines.images import ImagesPipeline
class ArticleImagePipeline(ImagesPipeline):
def item_completed(self, results, item, info):
for ok, value in results:
image_file_path = value[“path”]
item[“front_img_path”] = image_file_path
return item
更改settings文件:
ITEM_PIPELINES = {
‘ArticleSprider.pipelines.ArticlespriderPipeline’: 300,
# ‘scrapy.pipelines.images.ImagesPipeline’: 1 ,# 后面的数字越小,越优先执行
‘ArticleSprider.pipelines.ArticleImagePipeline’: 1,
}
把图片的url经过md5计算后的结果
创建一个保存常用方法的目录,utils,在utils下新建一个common文件,在common中编写一个计算MD5的方法,接着在解析文章的方法中导入这个方法,再调用方法填充到item实例
把item写成json文件
在pipelines中,写一个方法打开文件,然后把传过来的item转成dict,再转成json
然后用self.file.write(lines)写入文件中,接着关闭self.file.close()
import codecs # 可以避免很多编码问题
import json
class JsonWithEncodingPipeline(object):
def init(self):
self.file = codecs.open(‘article.json’, ‘w’, encoding=‘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 = {
‘ArticleSprider.pipelines.JsonWithEncodingPipeline’: 2,
# ‘scrapy.pipelines.images.ImagesPipeline’: 1 ,# 后面的数字越小,越优先执行
‘ArticleSprider.pipelines.ArticleImagePipeline’: 1,
}
4.将item保存到数据库
方法(一)
1 进入虚拟环境,安装 mysqlclient: pip install mysqlclient
2.定义pipelines
class MysqlPipeline(object):
def __init__(self): #连接数据库
self.conn = MySQLdb.connect('host', 'user', 'password', 'dbname', charset='utf8', use_unicode=True)
self.cursor = self.conn.cursor()
def process_item(self,item,sprider): #执行sql
insert_sql = "insert into jobbole_article(title,url,create_date,fav_nums) VALUES (%s,%s,%s,%s)"
self.cursor.execute(insert_sql, (item['title'], item['url'], item['create_date'], item['fav_nums']))
self.conn.commit()
3.在setting中配置pipeline: ‘BlizzaedSpider.pipelines.MysqlPipeline’: 1,
方法(二) 爬取数据多了之后插入速度会跟不上爬取速度,就会发生堵塞,所以考虑第二种方法
1 在setting文件最后加上 配置
MYSQL_HOST = "192.168.0.106"
MYSQL_DBNAMQ = "jobbole_article"
MYSQL_USER = "root"
MYSQL_PASSWORD = "root"
2 创建pipeline
from twisted.enterprise import adbapi
.....
class MysqlTwistedPipeline(object):
def __init__(self,dbpool):
self.dbpool = dbpool
@classmethod
# 通过这个注解定义这个函数,这个函数实际上就是一个setting,可以来读取setting的
def from_settings(cls, settings):
# 这个方法的名称是固定的,在初始化的时候就被scrapy调用
dbparms = dict(
host = settings["MYSQL_HOST"],
db = settings["MYSQL_DBNAME"],
user = settings["MYSQL_USER"],
password = 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):
# 使用twisted将mysql插入变成异步执行
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,create_date,fav_nums) VALUES (%s,%s,%s,%s)"
self.execute(insert_sql, (item['title'], item['url'], item['create_date'], item['fav_nums']))
#会自动commit
itemLoder
目的:为了使代码更便于维护,提高可配置性,可以把规则存到数据库,需要的时候再到数据库查询,做一个匹配映射,还可以简省代码
items:
from scrapy.loader import ItemLoader
class NgaItemLoder(ItemLoader):
default_output_processor = TakeFirst()
def update_time(value):
if value:
time = value.replace('上传于', '')
date = time.strip()
else:
date = datetime.datetime.now().date()
return date
def get_nums(value):
if value:
nums = int(value[0])
else:
nums = 0
return nums
class NgaspiderItem(scrapy.Item):
url = scrapy.Field()
url_id = scrapy.Field()
front_img_url = scrapy.Field()
front_img_url_path = scrapy.Field()
title = scrapy.Field()
update_time = scrapy.Field(
input_processor=MapCompose(update_time)
)
author = scrapy.Field()
playnum = scrapy.Field(
)
comment_count = scrapy.Field(
input_processor=MapCompose(get_nums)
)
like_count = scrapy.Field(
input_processor=MapCompose(get_nums)
)
使用:
def parse_details(self, response):
duowan = NgaspiderItem()
itemloder = NgaItemLoder(item = NgaspiderItem(), response = response)
itemloder.add_value("url", response.url)
itemloder.add_value("url_id", get_md5(response.url))
image_url_list = []
image_url_list.append(response.meta.get('front_img_url', ''))
itemloder.add_value("front_img_url", [image_url_list])
itemloder.add_css("title", ".play-title::text")
itemloder.add_xpath("update_time", '//*[@id="wrap"]/div/div[3]/div[1]/div[3]/div[1]/div[1]/span/text()')
itemloder.add_xpath('author', '//*[@id="wrap"]/div/div[3]/div[1]/div[3]/div[1]/div[1]/h3/a/text()')
itemloder.add_css('playnum', '#video_play_num::attr(data-play-num)')
itemloder.add_xpath('comment_count', '//*[@id="first_comment_count"]/text()')
itemloder.add_xpath('like_count', '//*[@id="wrap"]/div/div[3]/div[1]/div[3]/div[1]/div[3]/div[2]/a[3]/text()')
duowan = itemloder.load_item()
yield duowan
收集所有404url以及404页面数(数据收集器的使用)
# 收集所有404url以及404页面数
handle_httpstatus_list = [404]
def __init__(self):
self.fail_urls = []
def parse(self, response):
if response.status == 404:
self.fail_urls.append(response.url)
self.crawler.stats.inc_value('failed_url')