什么是爬虫
网络爬虫(Web Crawler或Spider)是一种自动化程序,通过互联网上的链接遍历并收集特定信息。通常,网络爬虫被用于从互联网上获取大量数据,例如从搜索引擎上抓取网页内容,或者从社交媒体平台上收集用户数据等。
网络爬虫能够模拟人类的浏览器行为,向目标服务器发送请求,并将响应解析成结构化数据。它们会根据预设规则和算法进行自动化抓取,同时能够处理正则表达式、解析HTML/XML格式文本等。收集到的数据可以进行分析、处理、存储等后续操作。
网络爬虫广泛应用于各个领域,包括搜索引擎、电商价格监控、舆情分析、金融市场研究等。
为什么要用python
实现爬虫技术的编程环境有很多种,Java、Python、C++等都可以用来爬虫。但很多人选择Python来写爬虫。
Python 作为一种高级编程语言,在爬虫领域具有诸多优势,包括:
-
语法简单、易于学习:Python 语法简洁明了,容易理解,对新手友好。即便没有编程经验也能快速上手。
-
社区活跃:Python 有庞大的社区支持,开发者可以通过在线文档、博客等方式获取相关技术资料和帮助。
-
丰富的第三方库:Python 有许多强大并且成熟的爬虫库,如 Scrapy、BeautifulSoup、Requests 等,可供使用。
-
跨平台:Python 可以在多种操作系统上运行,并且与其他编程语言兼容性良好,使得爬虫开发更加灵活。
-
处理文本数据能力强:爬虫需要处理大量的文本数据,Python 在文本处理方面有着非常出色的表现,如字符串处理、正则表达式等。
-
支持异步编程:Python 异步编程模型 asyncio,可避免网络请求阻塞,提高爬虫效率。
综上,Python 具有易学易用、社区活跃、丰富的第三方库、跨平台、处理文本数据能力强、支持异步编程等特点,使其成为非常适合做爬虫的编程语言。
为什么要用python爬虫框架
使用Python爬虫框架的好处包括:
-
自动化:使用框架可以轻松地编写自动化程序,用于抓取数据并存储为结构化数据。
-
快速开发:Python爬虫框架提供了开箱即用的功能和方法,使得开发爬虫变得更快捷和高效。
-
爬取效率高:爬虫框架针对不同的网站都有相应的优化策略,使得爬取效率得到了很大的提升,从而也加快了数据采集的速度。
-
数据处理:爬虫框架同样提供了一些数据处理的方法,例如:去重、清洗、解析等。这些功能可以让我们更容易地处理爬取到的数据。
-
可维护性强:使用Python爬虫框架可以使代码更加模块化,使得代码结构更加清晰,便于日后的维护。
什么是Scrapy
Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。 可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。它是很强大的爬虫框架,可以满足简单的页面爬取,比如可以明确获知url pattern的情况。它的特性有:HTML, XML源数据 选择及提取 的内置支持;提供了一系列在spider之间共享的可复用的过滤器(即 Item Loaders),对智能处理爬取数据提供了内置支持。
它有以下组件:
-
引擎 (engine): 该组件控制整个 scrapy 系统的执行流程。主要负责调度其他组件的运作,并掌握其中的数据流。
-
调度器 (scheduler): 对于爬取任务,Scrapy 使用调度器来安排爬虫执行的顺序。 它接受引擎发出的请求并将其排队,以便在下载器可用时(例如,在等待其他请求或下载完成后)执行请求。
-
下载器 (downloader): 该组件使 Scrapy 可以从互联网上下载所需内容。它处理引擎通过调度器分配的每个请求,并返回响应。
-
爬虫 (spiders): 爬虫是 Scrapy 中最重要的组件之一,它定义了如何爬取特定网站的数据。对于每个要爬取的网站,都需要编写一个自定义的 spider。当 Spider 接收到 Response 作为输入时,它将解析网站并提取所需的数据。
-
项目管道 (pipelines): 一旦数据被从 Spider 提取出来,可以将其传递给项目管道进行处理。管道是 Scrapy 中的一个可扩展组件,使用户能够将数据存储在数据库中、将其转换为 JSON 或 XML 格式等。
-
下载器中间件 (Downloader Middleware): 下载器中间件是一些钩子函数,可以介入 Scrapy 的下载器处理流程。例如,可以使用这些中间件来为每个请求添加代理、更改 User-Agent 等。
-
Spider 中间件 (Spider Middleware): Spider 中间件同样是一些钩子函数,可以介入 Scrapy 的爬虫处理流程。例如,可以使用这些中间件来对 Spider 输出的数据进行处理、监控爬虫运行情况等。
以上这些组件构成了 Scrapy 框架的主要建筑模块。通过它们的协作,Scrapy 可以实现高效、稳定的网站爬取。
开始自己的第一个Spider
创建项目
在开始抓取之前,必须设置一个新的 Scrapy 项目。输入您要存储代码的目录并运行:
首先在命令行创建Scrapy项目:
scrapy startproject myproject
这将会在当前目录下创建一个名为myproject的文件夹,里面包含以下文件:
myproject/
scrapy.cfg # 项目配置文件
myproject/ # Python模块,主要工作区域
__init__.py
items.py # 定义数据模型
middlewares.py # 中间件
pipelines.py # 数据处理管道
settings.py # 项目配置文件
spiders/ # 爬虫文件夹
__init__.py
接下来,我们在spiders文件夹中创建一个新的爬虫文件example.py,内容如下:
import scrapy
class ExampleSpider(scrapy.Spider):
name = "example"
allowed_domains = ["example.com"]
start_urls = [
"http://www.example.com",
]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
filename = "example.html"
with open(filename, 'wb') as f:
f.write(response.body)
self.log("Saved file %s" % filename)
以上代码定义了一个名为example的爬虫,它会从http://www.example.com开始抓取数据,并将响应保存到example.html文件中。
Spider 子类化scrapy.Spider
并定义了一些属性和方法:
name
: 标识scrapy。在一个项目中必须是唯一的,即不能为不同的Spiders设置相同的名称。start_requests()
: 必须返回一个可迭代的请求(你可以返回一个请求列表或编写一个生成器函数),scrapy将从中开始爬行。后续请求将从这些初始请求中依次生成。parse()
:将被调用以处理为每个请求下载的响应的方法。response 参数是 的一个实例TextResponse
,它保存页面内容并有更多有用的方法来处理它。
该parse()
方法通常解析响应,将抓取的数据提取为字典,并找到要遵循的新 URL 并Request从中创建新请求 ()。
如何运行我们的scrapy
要让我们的scrapy工作,请转到项目的顶级目录并运行:
scrapy crawl example
quotes
此命令使用我们刚刚添加的名称运行蜘蛛,它将发送一些对example.com域的请求。您将获得类似于此的输出:
... (omitted for brevity)
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Spider opened
2016-12-16 21:24:05 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2016-12-16 21:24:05 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://www.example.com/robots.txt> (referer: None)
2016-12-16 21:24:05 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://www.example.com> (referer: None)
2016-12-16 21:24:05 [quotes] DEBUG: Saved file example.html
2016-12-16 21:24:05 [scrapy.core.engine] INFO: Closing spider (finished)
...
现在,检查当前目录中的文件。您应该注意到已经创建了一个新文件:example.html,其中包含相应 URL 的内容,正如我们的parse
方法所指示的那样。
幕后发生了什么
Scrapy调度Spider
的方法scrapy.Request
返回的对象。start_requests
在收到每个响应后,它实例化Response对象并调用与请求关联的回调方法(在本例中为方法 parse
),将响应作为参数传递。
怎么提取数据
Python Scrapy提取数据有多种方式,以下是其中的一些:
-
XPath选择器:XPath是一种用于从HTML或XML文档中提取数据的语言。Scrapy使用XPath选择器来查找和提取HTML或XML文档中的数据。
-
CSS选择器:CSS选择器也可以用来从HTML文档中提取数据。Scrapy使用CSS选择器来查找和提取HTML文档中的数据。
-
正则表达式:正则表达式可以用来从文本中提取数据。Scrapy可以使用正则表达式来查找和提取HTML文档中的数据。
-
Item Loaders:Item Loaders是Scrapy中的一个实用工具,它可以将数据提取逻辑从Spider内部移动到单独的类中,并允许您定义如何处理、清理和验证数据。
-
JsonPath:JsonPath是一种用于从JSON文档中提取数据的语言。如果您正在爬取JSON API,则可以使用JsonPath来提取数据。
这些都是Python Scrapy提取数据的常见方式,开发者可以根据实际需求选择最合适的方式。
XPath
XPath是一种基于路径表达式的查询语言,可以定位和选择XML/HTML文档中的节点以及节点集。Scrapy使用XPath选择器来查找和提取HTML或XML文档中的数据。
以下是关于Python XPath的详细介绍及例子:
XPath语法
XPath语法类似于文件系统路径,由斜杠(/)分隔的一系列元素组成,每个元素都描述了一个节点或一组节点。XPath表达式可以包括元素名称、属性名称、通配符和谓词。
- 元素名称:元素名称指定要选取的节点类型。
- 属性名称:属性名称用@符号表示,用于选取特定属性值。
- 通配符:通配符(*)可以匹配任何节点。
- 谓词:谓词用方括号([])表示,用于筛选满足条件的节点。
以下是一个使用Python的XPath示例:
假设我们有以下HTML代码:
<html>
<head>
<title>This is a title</title>
</head>
<body>
<div id="content">
<h1>Welcome to my website</h1>
<p class="intro">This is the introduction paragraph.</p>
<ul>
<li><a href="http://www.example.com">Link 1</a></li>
<li><a href="http://www.example.com">Link 2</a></li>
<li><a href="http://www.example.com">Link 3</a></li>
</ul>
<p class="conclusion">This is the conclusion paragraph.</p>
</div>
</body>
</html>
要在Python中使用XPath来选择其中的元素,可以使用 lxml
库。例如,如果我们想选取所有链接的文本内容,可以使用以下代码:
from lxml import html
# 将上面的html代码保存到字符串变量page_content中
page_content = '''
<html>
<head>
<title>This is a title</title>
</head>
<body>
<div id="content">
<h1>Welcome to my website</h1>
<p class="intro">This is the introduction paragraph.</p>
<ul>
<li><a href="http://www.example.com">Link 1</a></li>
<li><a href="http://www.example.com">Link 2</a></li>
<li><a href="http://www.example.com">Link 3</a></li>
</ul>
<p class="conclusion">This is the conclusion paragraph.</p>
</div>
</body>
</html>
'''
# 将页面内容解析为HTML对象
tree = html.fromstring(page_content)
# 使用XPath选取所有链接的文本内容
links = tree.xpath('//a/text()')
# 输出结果
print(links)
这将输出以下结果:
['Link 1', 'Link 2', 'Link 3']
在这个例子中,我们使用 lxml
库中的 html
模块来解析页面内容并创建一个HTML对象。然后,我们使用XPath表达式 //a/text()
来选取所有链接的文本内容,并将结果存储在变量 links
中。最后,我们使用 print()
函数输出结果。
CSS选择器
CSS选择器使用基于类名、ID、标签名等属性来匹配并选取元素。Python中有多个库可以使用CSS选择器,例如 BeautifulSoup
和 pyquery
。
下面是一些常见的CSS选择器及其用法:
选择标签元素
选择所有p元素: p
选择类名
选择class为"example"的所有元素: .example
选择ID
选择id为"header"的元素: #header
选择子元素
选择ul元素下所有的li元素: ul li
选择相邻兄弟元素
选择class为"example"的元素后面第一个相邻的div元素: .example + div
选择后代元素
选择div元素内部所有class为"example"的元素: div .example
以下是在Python中使用CSS选择器的示例:
- 从HTML页面中提取所有段落文本:
from bs4 import BeautifulSoup
html = """
<html>
<body>
<h1>这是标题</h1>
<p class="intro">这是第一个段落。</p>
<p class="content">这是第二个段落。</p>
</body>
</html>
"""
soup = BeautifulSoup(html, 'html.parser')
paragraphs = soup.select('p')
for paragraph in paragraphs:
print(paragraph.text)
输出:
这是第一个段落。
这是第二个段落。
- 提取具有特定类的元素的属性值:
from bs4 import BeautifulSoup
html = """
<html>
<body>
<a href="http://www.example.com" class="link">链接</a>
<a href="http://www.google.com" class="link">谷歌</a>
</body>
</html>
"""
soup = BeautifulSoup(html, 'html.parser')
links = soup.select('.link')
for link in links:
print(link['href'])
输出:
http://www.example.com
http://www.google.com
- 使用层级结构查找元素:
from bs4 import BeautifulSoup
html = """
<html>
<body>
<div>
<h2>这是标题</h2>
<p>这是段落。</p>
</div>
<div>
<h2>这是另一个标题</h2>
<p>这是另一个段落。</p>
</div>
</body>
</html>
"""
soup = BeautifulSoup(html, 'html.parser')
divs = soup.select('body > div')
for div in divs:
print(div.h2.text)
print(div.p.text)
输出:
这是标题
这是段落。
这是另一个标题
这是另一个段落。
正则表达式
正则表达式是一种用来描述文本模式的语言,常常被用于文本处理、搜索、替换和数据提取等方面。Python是一个非常强大的正则表达式解析工具,内置re模块可以用于执行正则表达式操作。
下面是Python中正则表达式的详细介绍以及示例:
基本语法
- 字符匹配
- 匹配单个字符:使用
.
表示任意字符 - 匹配某个特定字符:在正则表达式中写入该字符即可
- 匹配一组字符:使用
[]
表示字符集,例如[abc]
会匹配a、b或c中的任意一个字符 - 匹配除了某些特定字符之外的任意字符:使用
[^]
表示,例如[^abc]
会匹配除了a、b、c之外的任意字符
- 重复匹配
- 匹配前一个字符0次或多次:使用
*
表示 - 匹配前一个字符1次或多次:使用
+
表示 - 匹配前一个字符0次或1次:使用
?
表示 - 匹配前一个字符n次:使用
{n}
表示 - 匹配前一个字符至少n次:使用
{n,}
表示 - 匹配前一个字符n到m次:使用
{n,m}
表示
- 特殊字符
- 匹配行首:使用
^
表示 - 匹配行尾:使用
$
表示 - 匹配数字:使用
\d
表示 - 匹配非数字:使用
\D
表示 - 匹配空白字符:使用
\s
表示 - 匹配非空白字符:使用
\S
表示 - 匹配字母、数字和下划线:使用
\w
表示 - 匹配除了字母、数字和下划线之外的任意字符:使用
\W
表示
以下是 Python 中的一些正则表达式示例:
- 寻找匹配的字符串:
import re
pattern = r"hello"
text = "hello world"
match_obj = re.search(pattern, text)
if match_obj:
print("Found", match_obj.group())
else:
print("Not found")
输出:Found hello
- 搜索和替换文本中的字符串:
import re
pattern = r"world"
text = "hello world"
new_text = re.sub(pattern, "Python", text)
print(new_text)
输出:hello Python
- 匹配多个可能的模式:
import re
pattern = r"cat|dog"
text = "I have a cat and a dog"
matches = re.findall(pattern, text)
print(matches)
输出:['cat', 'dog']
- 匹配指定数量的字符:
import re
pattern = r"\d{3}-\d{2}-\d{4}"
text = "My social security number is 123-45-6789"
match_obj = re.search(pattern, text)
if match_obj:
print("Found", match_obj.group())
else:
print("Not found")
输出:Found 123-45-6789
Item Loaders
在 Scrapy 中,Item Loaders 可以帮助我们更方便地处理爬取到的数据,并将其存储到 Item 对象中。它是一种支持链式调用的工具,可以在 Item 对象中添加或修改字段值。
Item Loaders 主要有以下几个特点:
可以使用默认值设置,以避免重复编写多个类似的代码;
支持通过输入处理器和输出处理器对数据进行预处理,以确保数据的一致性;
可以使用自定义的表达式(XPath、CSS 等)从爬取到的数据中提取数据;
可以轻松地扩展和定制 Item Loaders 功能。
下面是一个简单的 Python Item Loader 示例,用于从网页中提取数据并加载到 Scrapy 的 Item 中。
import scrapy
from scrapy.loader import ItemLoader
from myproject.items import MyItem
class MySpider(scrapy.Spider):
name = "myspider"
start_urls = [
"http://www.example.com",
]
def parse(self, response):
loader = ItemLoader(item=MyItem(), response=response)
loader.add_xpath("title", "//title/text()")
loader.add_xpath("description", "//meta[@name='description']/@content")
loader.add_value("url", response.url)
return loader.load_item()
在以上代码中,我们首先导入了 scrapy
和 ItemLoader
模块,以及 Scrapy 项目中定义的 MyItem
类。然后,我们定义了一个 Spider 类,该类继承自 scrapy.Spider
类,并定义了爬取的起始 URL。在 parse
方法中,我们创建了一个 ItemLoader 实例,将其与 MyItem
实例关联。然后,我们使用 add_xpath
方法添加了三个属性:title
、description
和 url
。最后,我们通过调用 loader.load_item()
将 Item 返回。
要使用此示例,请将代码复制到 Scrapy 项目的 spiders
目录下,并替换 myproject
和 MyItem
为您的项目名称和定义的 Item 类。然后,运行 Scrapy 爬虫即可。
JsonPath 是一种用于从 JSON 数据结构中提取信息的语言,类似于 XPath 用于 XML。它非常适合在 Python 中使用,因为许多 Python 库都内置了 JsonPath 的支持。
JsonPath
JsonPath 使用路径表达式来定位 JSON 中的元素。这些表达式类似于 Unix 文件系统中使用的路径名。以下是一些常用的 JsonPath 表达式:
$
: 根元素@
: 当前节点.
: 子节点..
: 子孙节点*
: 所有子节点[]
: 数组下标或属性名称[,]
: 多个下标或属性
下面是一个简单的例子,演示如何使用 JsonPath 在 Python 中解析 JSON 数据:
import json
from jsonpath_ng import parse
# 定义 JSON 数据
data = {
"name": "Bob",
"age": 30,
"email": "bob@example.com",
"pets": [
{"name": "Fluffy", "type": "cat"},
{"name": "Fido", "type": "dog"}
]
}
# 将 JSON 转换为字符串
json_str = json.dumps(data)
# 解析 JSON 字符串
json_data = json.loads(json_str)
# 使用 JsonPath 提取数据
name_expr = parse("$.name")
name = name_expr.find(json_data)[0].value
print(f"Name: {name}")
pet_expr = parse("$.pets[1].name")
pet_name = pet_expr.find(json_data)[0].value
print(f"Pet name: {pet_name}")
在以上代码中,我们首先定义了一个 JSON 数据结构。然后,我们将其转换为字符串,并使用 json.loads()
函数将其解析为 Python 对象。接下来,我们使用 parse()
函数创建了两个 JsonPath 表达式:$.name
和 $.pets[1].name
。这些表达式分别提取了 name
属性和第二个宠物的名称。
怎么存储数据
存储抓取数据的最简单方法是使用Feed exports,使用以下命令:
scrapy crawl quotes -O quotes.json
这将生成一个quotes.json包含所有已抓取项目的文件,并以JSON序列化。
命令-O行开关覆盖任何现有文件;use -oinstead 将新内容附加到任何现有文件。但是,附加到 JSON 文件会使文件内容无效 JSON。附加到文件时,请考虑使用不同的序列化格式,例如JSON 行:
scrapy crawl quotes -O quotes.json
JSON行格式很有用,因为它类似于流,您可以轻松地向其追加新记录。当你运行两次时,它没有 JSON 的相同问题。此外,由于每条记录都是单独的一行,因此您可以处理大文件而不必将所有内容都放入内存中,有像JQ这样的工具可以在命令行中帮助完成这项工作。