scrapy的工作原理
scrapy工作的步骤如下图所示:
- spiders获取的requests通过engine交给scheduler进行调度。
- engine从scheduler获得requests, 交给downloader进行下载。
- engine将downloader返回的response交给spiders进行解析。
- spiders从response中解析出items和requests后, 分别交给item pipelines和scheduler处理。
理论基础
正则表达式模式匹配
xpath使用路径表达式在xml和html中进行导航。
Xpath语法
表达式 说明
article 选取所有aricle元素所有子节点
/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|//ul 选取所有属于article元素子元素div的子元素p以及所有的span元素
//span[contains(@class,'vote-post-up')] 选取所有的class中含有vote-post-up的span子元素
CSS选择器语法
表达式 说明
* 选择所有节点
#container 选择id为container的节点
.container 选择所有class包含container的节点
li a 选择li下的所有a节点
ul + p 选择ul后面的第一个p元素
div#container > ul 选择id为container的div的第一个ul子元素
ul ~ p 选取与ul相邻的所有p元素
a[title] 选取所有有title属性的a元素
a[href="http://jobbole.com"] 选取所有href属性为http://jobbole.com的a元素
a[href*="jobbole"] 选取所有href属性包含jobbole的a元素
a[href^ ="http"] 选取所有href属性以http开头的a元素
a[href$=".jpg"] 选取所有href属性以.jpg结尾的a元素
input[type=radio]:checked 选取选中的radio元素
a[title] 选取所有有title属性的a元素
div:not(#container) 选取所有id非container的div属性
li:nth-child(3) 选取第三个li元素
tr:nth-child(2n) 选取第偶数个tr
a::text 获取a标签中的文本内容
a::attr(href) 获取a标签中href属性的值
深度优先和广度优先
一般情况下, 我们将网站域名之间的关系看作是一个树结构, 对于树结构中各节点的顺序分为深度优先和广度优先:
二叉树的深度优先算法实现
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)
二叉树的广度优先算法实现
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)
创建一个scrapy项目
我们先拿链家的二手房成交数据练个手, 先自行新建一个文件夹用于存储文件, 之后cd到该目录下, 新建一个scrapy项目:
scrapy startproject ZiruSpider(工程名)
用编译器打开项目,找到文件scrapy.cfg, 这个文件记录的是该项目的配置信息:
# Automatically created by: scrapy startproject
#
# For more information about the [deploy] section see:
# https://scrapyd.readthedocs.io/en/latest/deploy.html
[settings]
default = ZiruCrawler.settings
[deploy]
#url = http://localhost:6800/
project = ZiruCrawler
主目录下还有一个文件setting.py是项目的配置文件, 记录的是项目的一些配置信息:
其中的robotstxt_obey记录的是本项目是否遵循robots协议, 为了提高爬虫有效性, 一般这里改成False。
BOT_NAME = 'ZiruCrawler'
SPIDER_MODULES = ['ZiruCrawler.spiders']
NEWSPIDER_MODULE = 'ZiruCrawler.spiders'
# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'ZiruCrawler (+http://www.yourdomain.com)'
# Obey robots.txt rules
ROBOTSTXT_OBEY = False
pipelines.py是记录数据存储相关信息的模块;
middlewares.py是存放我们自己定义middlewares的模块;
items.py是类似django中的form, 定义数据保存的格式;
创建一个爬虫脚本
主目录下一个spiders文件夹是用于存放针对某个网站的爬虫脚本的。
至于这些脚本是如何生成的, 我打开项目根目录下,运行以下命令:
cd ZiruSpider
scrapy genspider lianjia(爬虫名) hz.lianjia.com/chengjiao/(目标网站地址)
这样我们就生成了一个名为lianjia.py的爬虫脚本文件, 专门用于 爬取hz.lianjia.com/chengjiao/。这个文件中start_urls中存放的就是这个爬虫开始爬取数据时使用到的第一个URL。
创建调试脚本
在最外层目录下创建一个用于调试的脚本文件main.py, scrapy调试的主要原理是通过脚本模拟在命令行执行命令:
from scrapy.cmdline import execute
import sys
import os
#将最外层目录路径放入系统目录下
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
execute(["scrapy","crawl","lianjia"])
运行以上脚本等价于在项目最外层目录下打开命令行运行以下命令:
scrapy crawl lianjia(爬虫名,即对应的爬虫脚本下name = []中的名字)
之后我们就可以通过断点的方式对main.py进行debug执行。
如果在windows 下报错"ImportError: No module named 'win32api'",则需要先输入以下命令安装一个依赖包:
pip3 install pypiwin32
scrapy的shell模式调试
打开终端执行以下命令:
scrapy shell <需要解析页面的URL>
通过这种方式可以使用shell对页面进行解析, 这样我们在编写爬虫, 解析页面时就可以用这个shell做检测。
之后执行以下语句可以得到title元素中data的内容
title.extract()
如果访问URL时需要添加headers可以写如下代码:
scrapy shell -s USER_AGENT="Mozilla/5.0 (Macintosh; Intel Mac OS X..." <需要解析页面的URL>
scrapy也可以通过css选择器来使用:
title = response.css('.entry-header')
css里的::text 等价于xpath里的text()
网友评论