python爬虫模块scrapy_Python爬虫框架Scrapy学习笔记原创

scrapy

[TOC]

开始

scrapy安装

首先手动安装windows版本的Twisted

https://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted

pip install Twisted-18.4.0-cp36-cp36m-win_amd64.whl

安装scrapy

pip install -i https://pypi.douban.com/simple/ scrapy

windows系统额外需要安装pypiwin32

pip install -i https://pypi.douban.com/simple pypiwin32

新建项目

开始一个项目

E:\svnProject> scrapy startproject TestSpider

生成一个新的爬虫(generate)

E:\svnProject> cd TestSpider

E:\svnProject\TestSpider> scrapy genspider dongfeng www.dongfe.com

启动一个爬虫

E:\svnProject\TestSpider> scrapy crawl dongfeng

SHELL模式

> scrapy shell http://www.dongfe.com/ # 命令行调试该网页

pycharm调试启动文件

E:\svnProject\TestSpider> vim main.py

import sys

import os

from scrapy.cmdline import execute

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

sys.path.append(BASE_DIR)

# scrapy crawl dongfeng

execute(["scrapy", "crawl", "dongfeng"])

项目基本配置

E:\svnProject\TestSpider\TestSpider> vim settings.py

ROBOTSTXT_OBEY = False # 不要遵循网站robots文件

XPATH

表达式

说明

/body

选出当前选择器的根元素body

/body/div

选取当前选择器文档的根元素body的所有div子元素

/body/div[1]

选取body根元素下面第一个div子元素

/body/div[last()]

选取body根元素下面最后一个div子元素

/body/div[last()-1]

选取body根元素下面倒数第二个div子元素

//div

选取所有div子元素(不论出现在文档任何地方)

body//div

选取所有属于body元素的后代的div元素(不论出现在body下的任何地方)

/body/@id

选取当前选择器文档的根元素body的id属性

//@class

选取所有元素的class属性

//div[@class]

选取所有拥有class属性的div元素

//div[@class='bold']

选取所有class属性等于bold的div元素

//div[contains(@class,'bold')]

选取所有class属性包含bold的div元素

/div/*

选取当前文档根元素div的所有子元素

//*

选取文档所有节点

//div[@*]

获取所有带属性的div元素

//div/a | //div/p

选取所有div元素下面的子元素a和子元素p(并集)

//p[@id='content']/text()

选取id为content的p标签的内容(子元素的标签和内容都不会获取到)

> 注意: XPATH在选择时,参考的是HTML源码,而不是JS加载后的HTML代码

操作例子

title_selector = response.xpath("//div[@class='entry-header']/h1/text()")

title_str = title_selector.extract()[0]

CSS选择器

表达式

说明

*

选择所有节点

#container

选择Id为container的节点

.container

选取所有包含container类的节点

li a

选取所有li下的所有后代a元素(子和孙等所有的都会选中)

ul + p

选取ul后面的第一个相邻兄弟p元素

div#container > ul

选取id为container的div的所有ul子元素

ul ~ p

选取与ul元素后面的所有兄弟p元素

a[title]

选取所有有title属性的a元素

选取所有href属性等于http://taobao.com的a元素

a[href*='taobao']

选取所有href属性包含taobao的a元素

a[href^='http']

选取所有href属性开头为http的a元素

a[href$='.com']

选取所有href属性结尾为.com的a元素

input[type=radio]:checked

选取选中的radio的input元素

div:not(#container)

选取所有id非container的div元素

li:nth-child(3)

选取第三个li元素

tr:nth-child(2n)

选取偶数位的tr元素

a::attr(href)

获取所有a元素的href属性值

操作例子

h1_selector = response.css(".container h1::text") # 选取h1标题的内容

h1_str = h1_selector.extract_first() # 取出数组第一个,如果没有为空

爬虫

爬取某网站文章列表例子

>>> vim ArticleSpider/spiders/jobbole.py

import scrapy

from scrapy.http import Request

from urllib import parse

import re

from ArticleSpider.items import ArticleItem

from ArticleSpider.utils.common import get_md5 # url转md5

class JobboleSpider(scrapy.Spider):

name = 'jobbole'

allowed_domains = ['blog.jobbole.com']

start_urls = ['http://blog.jobbole.com/all-posts/']

def parse(self, response):

"""

文章列表页的文章链接解析

:param response:

:return:

"""

css = "#archive > .post > .post-thumb > a"

article_urls_selector = response.css(css) # 获取当前列表页所有文章的链接

for article_url_selector in article_urls_selector:

head_img_url = article_url_selector.css("img::attr(src)").extract_first() # 封面URL

head_img_full_url = parse.urljoin(response.url, head_img_url) # 封面图片完整URL

article_url = article_url_selector.css("a::attr(href)").extract_first("") # 文章URL

article_full_url = parse.urljoin(response.url, article_url) # 智能的拼接URL,相对地址直接对接;绝对地址只取出域名对接;完全地址不对接,直接获取。

yield Request(url=article_full_url, callback=self.article_parse, meta={"head_img_full_url": head_img_full_url}) # 请求文章详情页并设置回调函数解析内容和meta传参

next_url = response.css(".next.page-numbers::attr(href)").extract_first("")

if next_url:

yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse) # 下一页文章列表使用递归

def article_parse(self, response):

"""

文章详情页的内容解析

:param response:

:return:

"""

title = response.css(".grid-8 .entry-header > h1::text").extract_first("") # 标题内容

add_time = response.css(".grid-8 .entry-meta p::text").extract_first("")

add_time_match = re.match("[\s\S]*?(\d{2,4}[/-]\d{1,2}[/-]\d{1,2})[\s\S]*", add_time)

if add_time_match:

add_time = add_time_match.group(1)

else:

add_time = add_time.strip()

content = response.css(".grid-8 .entry").extract_first("") # 文章内容

star = response.css("h10::text").extract_first("") # 点赞数

head_img_url = response.meta.get("head_img_full_url") # 封面URL,通过上一个解释器在回调时传参得到的数据

# 把数据整理到item

article_item = ArticleItem() # 实例化一个item

article_item["title"] = title

article_item["content"] = content

# 把时间字符串转为可保存mysql的日期对象

try:

add_time = datetime.datetime.strptime(add_time, "%Y/%m/%d").date()

except Exception as e:

add_time = datetime.datetime.now().date()

article_item["add_time"] = add_time

article_item["star"] = star

article_item["head_img_url"] = [head_img_url] # 传递URL图片保存列表供ImagesPipeline使用

article_item["url"] = response.url

article_item["url_object_id"] = get_md5(response.url) # 获取url的md5值

yield article_item # 传递到pipeline。请看settings.py中ITEM_PIPELINES字典

# Item设计,类似于django的表单类

>>> vim ArticleSpider/items.py

import scrapy

class ArticleItem(scrapy.Item):

title = scrapy.Field() # 标题

content = scrapy.Field() # 内容

add_time = scrapy.Field() # 文章添加时间

url = scrapy.Field() # 文章URL

url_object_id = scrapy.Field() # URL的MD5值

head_img_url = scrapy.Field() # 封面图URL

head_img_path = scrapy.Field() # 封面图本地路径

star = scrapy.Field() # 点赞数

>>> vim ArticleSpider/spiders/settings.py

# 修改配置文件,去掉这个地方的注释,当爬虫解析函数返回Item对象时,需要经过这个管道

# Item管道

ITEM_PIPELINES = { # item的pipeline处理类;类似于item中间件

'ArticleSpider.pipelines.ArticlespiderPipeline': 300, # 处理顺序是按数字顺序,1代表第一个处理

}

# URL转md5函数

>>> create ArticleSpider/utils/__init__.py # 公共工具包

>>> vim common.py

import hashlib

def get_md5(url): # 获取URL的MD5值

if isinstance(url, str): # 如果是Unicode字符串

url = url.encode("utf-8")

m = hashlib.md5()

m.update(url) # 只接受UTF-8字节码

return m.hexdigest()

图片自动下载

>>> vim ArticleSpider/settings.py

PROJECT_DIR = os.path.join(BASE_DIR, "ArticleSpider")

# 修改配置文件,去掉这个地方的注释,当爬虫解析函数返回Item对象时,需要经过这个管道

# Item管道

ITEM_PIPELINES = { # item的pipeline处理类;类似于item中间件

'ArticleSpider.pipelines.ArticlespiderPipeline': 300, # 处理顺序是按数字顺序,1代表第一个处理

'scrapy.pipelines.images.ImagesPipeline': 1,

}

IMAGES_URLS_FIELD = "head_img_url" # 图片URL的字段名

IMAGES_STORE = os.path.join(PROJECT_DIR, "images") # 图片本地保存地址

# IMAGES_MIN_HEIGHT = 100 # 接收图片的最小高度

# IMAGES_MIN_WIDTH = 100 # 接收图片的最小宽度

图片自动下载自定义类

>>> vim ArticleSpider/settings.py

ITEM_PIPELINES = {

...

#'scrapy.pipelines.images.ImagesPipeline': 1,

'ArticleSpider.pipelines.ArticleImagePipeline': 1,

}

>>> vim ArticleSpider/pipelines.py

class ArticleImagePipeline(ImagesPipeline):

def item_completed(self, results, item, info):

if "head_img_url" in item: # 只处理有数据的URL

for ok, value in results: # 默认是多个图片URL,其实只传递了一个,所以results内只有一个

image_file_path = value["path"] # 获取图片保存的本地路径

item["head_img_path"] = image_file_path

return item # 返回item,下一个pipeline接收处理

数据保存

把item数据导出到json文件中

vim ArticleSpider/pipelines.py

import codecs # 文件操作模块

import json

# 把item保存到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" # 关闭ascii保存,因为有中文

self.file.write(lines)

return item

def spider_closed(self, spider):

# 当爬虫关闭时

self.file.close()

注册到item管道配置中

vim ArticleSpider/settings.py

ITEM_PIPELINES = {

'...',

'ArticleSpider.pipelines.JsonWithEncodingPipeline': 2,

}

使用自带的模块导出json文件

>>> vim ArticleSpider/pipelines.py

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 close_spider(self, spider):

self.exporter.finish_exporting()

self.file.close()

注册到item管道配置中

vim ArticleSpider/settings.py

ITEM_PIPELINES = {

'...',

'ArticleSpider.pipelines.JsonExporterPipeline': 2,

}

使用mysql保存

# 安装mysql驱动

>>> pip install mysqlclient

# centos需要另外安装驱动

>>> sudo yum install python-devel mysql-devel

使用同步的机制写入mysql

import MySQLdb

class MysqlPipeline(object):

def __init__(self):

self.conn = MySQLdb.connect('dongfe.com', 'root', 'Xiong123!@#', 'article_spider', charset="utf8", use_unicode=True)

self.cursor = self.conn.cursor()

def process_item(self, item, spider):

insert_sql = """

insert into article(title, url, add_time, star)

values (%s, %s, %s, %s)

"""

self.cursor.execute(insert_sql, (item['title'], item['url'], item['add_time'], item['star']))

self.conn.commit()

使用异步的机制写入mysql

import MySQLdb

import MySQLdb.cursors

from twisted.enterprise import adbapi

class MysqlTwistedPipline(object):

def __init__(self, dbpool):

self.dbpool = dbpool

@classmethod

def from_settings(cls, settings):

pass # 这个方法会把settings文件传进来

dbparms = dict(

host="dongfe.com",

db="article_spider",

user="root",

passwd="Xiong123!@#",

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) # 处理异常

def do_insert(self, cursor, item):

# 执行具体的插入

insert_sql = """

insert into article(title, url, add_time, star)

values (%s, %s, %s, %s)

"""

cursor.execute(insert_sql, (item['title'], item['url'], item['add_time'], item['star']))

def handle_error(self, failure, item, spider):

# 处理异步插入的异常

print(failure)

item loader

直接将Item和CSS选择器绑定到一起,直接把选择出来的数据放入Item中。

item loader的一般使用

>>> vim ArticleSpider/spiders/jobbole.py

class JobboleSpider(scrapy.Spider):

name = 'jobbole'

allowed_domains = ['blog.jobbole.com']

start_urls = ['http://blog.jobbole.com/all-posts/']

...

# 文章详情页的内容解析

def article_parse(self, response):

# 通过item loader加载item

item_loader = ItemLoader(item=ArticleItem(), response=response)

item_loader.add_css("title", ".grid-8 .entry-header > h1::text")

item_loader.add_css("content", ".grid-8 .entry")

item_loader.add_css("add_time", ".grid-8 .entry-meta p::text")

item_loader.add_value("url", response.url)

item_loader.add_value("url_object_id", get_md5(response.url))

item_loader.add_value("head_img_url", [head_img_url])

item_loader.add_css("star", "h10::text")

article_item = item_loader.load_item()

yield article_item # 传递到pipeline。请看settings.py中ITEM_PIPELINES字典

> 使用Item Loader的两个问题:

>

> 1. 原始数据需要处理

> 1. 解决办法:在Item内使用字段的处理器

> 2. 不管数据有几个,获取的是一个数组

> 1. 解决办法:在Item内字段处理器中使用TakeFirst()方法

配合item 的processor处理器的使用

>>> vim ArticleSpider/items.py

# MapCompose:可以调用多个函数依次运行

# TakeFirst: 与extract_first()函数一样,只选择数组第一个数据

# Join: 把数组用符号连接成字符串,比如Join(",")

from scrapy.loader.processors import MapCompose, TakeFirst, Join

# add_time键处理函数

def date_convert1(value):

add_time_match = re.match("[\s\S]*?(\d{2,4}[/-]\d{1,2}[/-]\d{1,2})[\s\S]*", value)

if add_time_match:

add_time = add_time_match.group(1)

else:

add_time = value.strip()

return add_time

def date_convert2(value):

try:

add_time = datetime.datetime.strptime(value, "%Y/%m/%d").date()

except Exception as e:

add_time = datetime.datetime.now().date()

return add_time

class ArticleItem(scrapy.Item):

...

add_time = scrapy.Field(

input_processor=MapCompose(date_convert1, date_convert2), # 处理原始数据

output_processor=TakeFirst() # 只取数组中第一个数据

) # 文章添加时间

...

自定义item loader

> 可以设置默认的default_output_processor = TakeFirst()

>>> vim ArticleSpider/items.py

from scrapy.loader import ItemLoader

class ArticleItemLoader(ItemLoader):

# 自定义item loader

default_output_processor = TakeFirst()

# 默认item处理器,什么都不做

def default_processor(value):

return value

class ArticleItem(scrapy.Item):

...

head_img_url = scrapy.Field(

# 图像URL需要一个数组类型,不取第一个数据,定义一个默认处理器覆盖掉

# 另外注意在使用sql保存时,需要取出数组第一个

output_processor=default_processor

) # 封面图URL

...

##########################################################################################

>>> vim ArticleSpider/spiders/jobbole.py

from ArticleSpider.items import ArticleItemLoader

class JobboleSpider(scrapy.Spider):

name = 'jobbole'

allowed_domains = ['blog.jobbole.com']

start_urls = ['http://blog.jobbole.com/all-posts/']

...

# 文章详情页的内容解析

def article_parse(self, response):

...

# 通过item loader加载item

item_loader = ArticleItemLoader(item=ArticleItem(), response=response)

...

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值