原文地址:http://blog.csdn.net/qq_23079443/article/details/73920584
基于Scrapy、Redis、elasticsearch和django打造一个完整的搜索引擎网站
未来是什么时代?是数据时代!数据分析服务、互联网金融,数据建模、自然语言处理、医疗病例分析……越来越多的工作会基于数据来做,而爬虫正是快速获取数据最重要的方式,相比其它语言,Python爬虫更简单、高效
一、基础知识学习:
1. 爬取策略的深度优先和广度优先
目录:
- 网站的树结构
- 深度优先算法和实现
- 广度优先算法和实现
网站url树结构分层设计:
- bogbole.com
- blog.bogbole.com
- python.bogbole.com
环路链接问题:
从首页到下面节点。
但是下面的链接节点又会有链接指向首页
所以:我们需要对于链接进行去重
1. 深度优先
2. 广度优先
跳过已爬取的链接
对于二叉树的遍历问题
深度优先(递归实现):
顺着一条路,走到最深处。然后回头
广度优先(队列实现):
分层遍历:遍历完儿子辈。然后遍历孙子辈
Python实现深度优先过程code:
def depth_tree(tree_node):
if tree_node is not None:
print (tree_node._data)
if tree_node._left is not None:
return depth_tree(tree_node.left)
if tree_node._right is not None:
return depth_tree(tree_node,_right)
Python实现广度优先过程code:
def level_queue(root):
#利用队列实现树的广度优先遍历
if root is None:
return
my_queue = []
node = root
my_queue.append(node)
while my_queue:
node = my_queue.pop(0)
print (node.elem)
if node.lchild is not None:
my_queue.append(node.lchild)
if node.rchild is not None:
my_queue.append(node.rchild)
2. 爬虫网址去重策略
- 将访问过的url保存到数据库中
- 将url保存到set中。只需要O(1)的代价就可以查询到url
100000000*2byte*50个字符/1024/1024/1024 = 9G
- url经过md5等方法哈希后保存到set中,将url压缩到固定长度而且不重复
- 用bitmap方法,将访问过的url通过hash函数映射到某一位
- bloomfilter方法对bitmap进行改进,多重hash函数降低冲突
scrapy去重使用的是第三种方法:后面分布式scrapy-redis会讲解bloomfilter方法。
3. Python字符串编码问题解决:
- 计算机只能处理数字,文本转换为数字才能处理,计算机中8个bit作为一个字节,
所以一个字节能表示的最大数字就是255 - 计算机是美国人发明的,所以一个字节就可以标识所有单个字符
,所以ASCII(一个字节)编码就成为美国人的标准编码 - 但是ASCII处理中文明显不够,中文不止255个汉字,所以中国制定了GB2312编码
,用两个字节表示一个汉字。GB2312将ASCII也包含进去了。同理,日文,韩文,越来越多的国家为了解决这个问题就都发展了一套编码,标准越来越多,如果出现多种语言混合显示就一定会出现乱码 - 于是unicode出现了,它将所有语言包含进去了。
- 看一下ASCII和unicode编码:
- 字母A用ASCII编码十进制是65,二进制 0100 0001
- 汉字”中” 已近超出ASCII编码的范围,用unicode编码是20013二进制是01001110 00101101
- A用unicode编码只需要前面补0二进制是 00000000 0100 0001
- 乱码问题解决的,但是如果内容全是英文,unicode编码比ASCII编码需要多一倍的存储空间,传输也会变慢。
- 所以此时出现了可变长的编码”utf-8” ,把英文:1字节,汉字3字节,特别生僻的变成4-6字节,如果传输大量的英文,utf8作用就很明显。
**读取文件,进行操作时转换为unicode编码进行处理** **保存文件时,转换为utf-8编码。以便于传输** 读文件的库会将转换为unicode *python2 默认编码格式为`ASCII`,Python3 默认编码为 `utf-8`*
#python3
import sys
sys.getdefaultencoding()
s.encoding("utf-8")
#python2
import sys
sys.getdefaultencoding()
s = "我和你"
su = u"我和你"
~~s.encode("utf-8")#会报错~~
s.decode("gb2312").encode("utf-8")
su.encode("utf-8")
二、伯乐在线爬取所有文章
1. 初始化文件目录
基础环境
- python 3.5.1
- JetBrains PyCharm 2016.3.2
- mysql+navicat
为了便于日后的部署:我们开发使用了虚拟环境。
pip install virtualenv
pip install virtualenvwrapper-win
安装虚拟环境管理
mkvirtualenv articlespider3
创建虚拟环境
workon articlespider3
直接进入虚拟环境
deactivate
退出激活状态
workon
知道有哪些虚拟环境
scrapy项目初始化介绍
自行官网下载py35对应得whl文件进行pip离线安装
Scrapy 1.3.3
**命令行创建scrapy项目**
cd desktop
scrapy startproject ArticleSpider
**scrapy目录结构** scrapy借鉴了django的项目思想
scrapy.cfg
:配置文件。setings.py
:设置
SPIDER_MODULES = ["ArticleSpider.spiders"] #存放spider的路径
NEWSPIDER_MODULE = "ArticleSpider.spiders"
pipelines.py:
做跟数据存储相关的东西
middilewares.py:
自己定义的middlewares 定义方法,处理响应的IO操作
__init__.py:
项目的初始化文件。
items.py:
定义我们所要爬取的信息的相关属性。Item对象是种类似于表单,用来保存获取到的数据
**创建我们的spider**
cd ArticleSpider
scrapy genspider jobbole blog.jobbole.com
可以看到直接为我们创建好的空项目里已经有了模板代码。如下:
# -*- coding: utf-8 -*-
import scrapy
class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["blog.jobbole.com"]
# start_urls是一个带爬的列表,
#spider会为我们把请求下载网页做到,直接到parse阶段
start_urls = ["http://blog.jobbole.com/"]
def parse(self, response):
pass
scray在命令行启动某一个Spyder的命令:
scrapy crawl jobbole
**在windows报出错误** `ImportError: No module named ‘win32api’`
pip install pypiwin32#解决
**创建我们的调试工具类*** 在项目根目录里创建main.py 作为调试工具文件
# _*_ coding: utf-8 _*_
__author__ = "mtianyan"
__date__ = "2017/3/28 12:06"
from scrapy.cmdline import execute
import sys
import os
#将系统当前目录设置为项目根目录
#os.path.abspath(__file__)为当前文件所在绝对路径
#os.path.dirname为文件所在目录
#H:CodePathspiderArticleSpidermain.py
#H:CodePathspiderArticleSpider
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
#执行命令,相当于在控制台cmd输入改名了
execute(["scrapy", "crawl" , "jobbole"])
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
**settings.py的设置不遵守reboots协议** `ROBOTSTXT_OBEY = False` 在jobble.py打上断点:
def parse(self, response):
pass
可以看到他返回的htmlresponse对象: 对象内部:
- body:网页内容
- _DEFAULT_ENCODING= ‘ascii’
- encoding= ‘utf-8’
可以看出scrapy已经为我们做到了将网页下载下来。而且编码也进行了转换.
2. 提取伯乐在线内容
xpath的使用
xpath让你可以不懂前端html,不看html的详细结构,只需要会右键查看就能获取网页上任何内容。速度远超beautifulsoup。 目录:
1. xpath简介
2. xpath术语与语法
3. xpath抓取误区:javasrcipt生成html与html源文件的区别
4. 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的属性 |
xpath语法-谓语:
表达式 |
说明 |
/article/div[1 |
选取属于article子元素的第一个div元素 |
/article/div[last()] |
选取属于article子元素的最后一个div元素 |
/article/div[last()-1] |
选取属于article子元素的倒数第二个div元素 |
//div[@color] |
选取所有拥有color属性的div元素 |
//div[@color=’red’] |
选取所有color属性值为red的div元素 |
xpath语法:
表达式 |
说明 |
/div/* |
选取属于div元素的所有子节点 |
//* |
选取所有元素 |
//div[@*] |
选取所有带属性的div 元素 |
//div/a 丨//div/p |
选取所有div元素的a和p元素 |
//span丨//ul |
选取文档中的span和ul元素 |
article/div/p丨//span |
选取所有属于article元素的div元素的p元素以及文档中所有的 span元素 |
xpath抓取误区
firebugs插件
取某一个网页上元素的xpath地址
如:http://blog.jobbole.com/110287/
在标题处右键使用firebugs查看元素。
然后在<h1>2016 腾讯软件开发面试题(部分)</h1>
右键查看xpath
# -*- coding: utf-8 -*-
import scrapy
class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["blog.jobbole.com"]
start_urls = ["http://blog.jobbole.com/110287/"]
def parse(self, response):
re_selector = response.xpath("/html/body/div[3]/div[3]/div[1]/div[1]/h1")
# print(re_selector)
pass
调试debug可以看到
re_selector =(selectorlist)[]
可以看到返回的是一个空列表,
列表是为了如果我们当前的xpath路径下还有层级目录时可以进行选取
空说明没取到值:
我们可以来chorme里观察一下
chorme取到的值
//*[@id="post-110287"]/div[1]/h1
chormexpath代码
# -*- coding: utf-8 -*-
import scrapy
class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["blog.jobbole.com"]
start_urls = ["http://blog.jobbole.com/110287/"]
def parse(self, response):
re_selector = response.xpath("//*[@id="post-110287"]/div[1]/h1")
# print(re_selector)
pass
可以看出此时可以取到值
分析页面,可以发现页面内有一部html是通过JavaScript ajax交互来生成的,因此在f12检查元素时的页面结构里有,而xpath不对
xpath是基于html源代码文件结构来找的
xpath可以有多种多样的写法:
re_selector = response.xpath("/html/body/div[1]/div[3]/div[1]/div[1]/h1/text()")
re2_selector = response.xpath("//*[@id="post-110287"]/div[1]/h1/text()")
re3_selector = response.xpath("//div[@class="entry-header]/h1/text()")
推荐使用id型。因为页面id唯一。
推荐使用class型,因为后期循环爬取可扩展通用性强。
通过了解了这些此时我们已经可以抓取到页面的标题,此时可以使用xpath利器照猫画虎抓取任何内容。只需要点击右键查看xpath。
开启控制台调试
scrapy shell http://blog.jobbole.com/110287/
完整的xpath提取伯乐在线字段代码
# -*- coding: utf-8 -*-
import scrapy
import re
class JobboleSpider(scrapy.Spider):
name = "jobbole"
allowed_domains = ["blog.jobbole.com"]
start_urls = ["http://blog.jobbole.com/110287/"]
def parse(self, response):
#提取文章的具体字段
title = response.xpath("//div[@class="entry-header"]/h1/text()").extract_first("")
create_date = response.xpath("//p[@class="entry-meta-hide-on-mobile"]/text()").extract()[0].strip().replace("·","").strip()
praise_nums = response.xpath("//span[contains(@class, "vote-post-up")]/h10/text()").extract()[0]
fav_nums = response.xpath("//span[contains(@class, "bookmark-btn")]/text()").extract()[0]
match_re = re.match(".*?(d+).*", fav_nums)
if match_re:
fav_nums = match_re.group(1)
comment_nums = response.xpath("//a[@href="#article-comment"]/span/text()").extract()[0]
match_re = re.match(".*?(d+).*", comment_nums)
if match_re:
comment_nums = match_re.group(1)
content = response.xpath("//div[@class="entry"]").extract()[0]
tag_list = response.xpath("//p[@class="entry-meta-hide-on-mobile"]/a/text()").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
tags = ",".join(tag_list)
pass
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
css选择器的使用:
# 通过css选择器提取字段
# front_image_url = response.meta.get("front_image_url", "") #文章封面图
title = response.css(".entry-header h1::text").extract_first()
create_date = response.css("p.entry-meta-hide-on-mobile::text").extract()[0].strip().replace("·","").strip()
praise_nums = response.css(".vote-post-up h10::text").extract()[0]
fav_nums = response.css(".bookmark-btn::text").extract()[0]
match_re = re.match(".*?(d+).*", fav_nums)
if match_re:
fav_nums = int(match_re.group(1))
else:
fav_nums = 0
comment_nums = response.css("a[href="#article-comment"] span::text").extract()[0]
match_re = re.match(".*?(d+).*", comment_nums)
if match_re:
comment_nums = int(match_re.group(1))
else:
comment_nums = 0
content = response.css("div.entry").extract()[0]
tag_list = response.css("p.entry-meta-hide-on-mobile a::text").extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
tags = ",".join(tag_list)
pass
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
3. 爬取所有文章
yield关键字
#使用request下载详情页面,下载完成后回调方法parse_detail()提取文章内容中的字段
yield Request(url=parse.urljoin(response.url,post_url),callback=self.parse_detail)
scrapy.http import Request下载网页