%pip install requests httpx parsel playwright --upgrade9 网络爬虫
9.1 网页基础知识
核心概念
什么是网站?
- 网站是通过互联网提供的一组相互链接的网页。
- 网站的组成部分:
- 前端:用户可见的部分,包括网页结构(HTML)、样式(CSS)和交互功能(JavaScript)。
- 后端:运行在服务器上的部分,负责逻辑处理、数据存储和提供 API 接口。
网络如何工作
- 域名和 IP 地址:
- 域名(如
example.com)是方便人类记忆的网络地址,通过 DNS(域名系统)映射到具体的 IP 地址。
- 域名(如
- HTTP 和 HTTPS 协议:
- 超文本传输协议(HTTP)定义了客户端和服务器之间的通信规则。
- HTTPS 是加密版本,确保数据传输的安全性。
- 客户端请求和服务器响应:
- 请求包含方法(如
GET或POST)、URL、头部信息等。 - 响应包含状态码(如 200 表示成功)、内容类型(如 HTML)和具体的数据。
- 请求包含方法(如
- 域名和 IP 地址:
基本网页技术
- HTML(超文本标记语言):
- 定义网页的结构,包括标题、段落、链接、图片等。
- 示例:
<html> <head> <title>示例网页</title> </head> <body> <h1>欢迎来到示例网页</h1> <p>这是一个段落。</p> </body> </html> - CSS(层叠样式表):
- 控制网页的外观样式(颜色、字体、布局等)。
- 示例:
<style> h1 { color: red; } p { font-size: 30px; } </style> - JavaScript:
- 为网页添加交互性(如按钮点击、动态加载内容)。
- 示例:
<button id="myButton">Click Me</button> <p id="message"></p> <script> // Get references to the button and the paragraph const button = document.getElementById("myButton"); const message = document.getElementById("message"); // Add an event listener to the button button.addEventListener("click", () => { // Change the text content of the paragraph message.textContent = "You clicked the button!"; }); </script>
- 客户端-服务器模型
- 客户端:
发送请求到服务器。
请求requests示例:浏览器、Python 爬虫脚本。
- 服务器:
- 接收请求并返回响应。
响应response - 示例:托管网页的服务器、提供 API 服务的服务器。
- 响应response
- 响应的内容类型:
text,如HTML、CSS、JavaScriptimage,如 JPEG、PNGpdf,如 PDF 文档json,书写格式类似于python的字典dictxml,类似于html的标记语言
- 接收请求并返回响应。
### HTTP 状态码简介
- 200:请求成功。
- 404:资源未找到。
- 500:服务器内部错误。
### 浏览器开发者工具
- 检查网页元素:右键点击网页,选择“检查”可以打开开发者工具。
- Network 标签:查看页面加载过程中所有请求和响应。
- Elements 标签:查看网页的 HTML 和 CSS 结构。
### 演示
- 打开浏览器并访问一个示例网站,例如
https://www.jnu.edu.cn。 - 使用开发者工具查看:
- HTML 元素结构。
- 页面中加载的 CSS 和 JavaScript 文件。
- 通过 Network 标签查看页面加载时的网络请求。
- 豆瓣读书 https://book.douban.com/tag/小说
- HTML(超文本标记语言):
9.2 爬虫基础
安装所需的库:
安装 Playwright 浏览器(在终端运行):
playwright install chromium分析网址规律
网址包含信息
是否需要load
javascriptresponse是否为json数据
是否需要用模拟浏览器
requests库
找到网页url规律后,我们需要将这些url对应的网页数据下载下来,这里就用到requests库文档链接。
requests两种访问方法 ,两者都返回Response对象:
| requests常用函数 | 参数解读 |
|---|---|
| requests.get(url, params, verify) | 发起get访问,返回Response对象;除非需要传参,否则不需要用params和verify参数 |
| requests.post(url, data) | 发起post访问,返回Response对象;除非需要传参,否则不需要用data参数 |
import requests
url = "http://www.ruanyifeng.com/blog/archives.html"
try:
r = requests.get(url)
print(type(r))
print(r.status_code)
except Exception as e:
print(e)<class 'requests.models.Response'>
200
import requests
headers = {'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36'
}
url = 'https://book.douban.com/tag/小说?start=20&type=T'
try:
r = requests.get(url, headers = headers)
print(type(r))
print(r.status_code)
except Exception as e:
print(e)<class 'requests.models.Response'>
200
注意Response后面带有的状态码: - 2开头表示访问正常 - 4开头,比如403表示爬虫被网站封锁 - 5开头表示服务器出问题
Response响应对象
| Response对象的方法 | 作用 |
|---|---|
| Response.json() | 获得json格式网页数据 |
| Response.text | 获得html网页数据 |
| Response.content | 获得网页二进制文件数据,常常用该方法爬取图片、视频等数据 |
| Response.encoding | 更改网页数据的编码方式。除非使用Response.text得到的html中文本是乱码的,否则一般不用Response.encoding |
| Response.status_code | 查看当前访问的状态码, 200 表示访问正常, 4开头 的状态码表示访问出问题, 5开头 的状态码表示服务器出问题 |
以上三种方法,最常用的是 text和json方法, 分别对应于 html网页数据和json网页数据
r.status_code200
print(r.text[:1000])
<!DOCTYPE html>
<html lang="zh-cmn-Hans" class="ua-windows ua-webkit book-new-nav">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>
豆瓣图书标签: 小说
</title>
<script>!function(e){var o=function(o,n,t){var c,i,r=new Date;n=n||30,t=t||"/",r.setTime(r.getTime()+24*n*60*60*1e3),c="; expires="+r.toGMTString();for(i in o)e.cookie=i+"="+o[i]+c+"; path="+t},n=function(o){var n,t,c,i=o+"=",r=e.cookie.split(";");for(t=0,c=r.length;t<c;t++)if(n=r[t].replace(/^\s+|\s+$/g,""),0==n.indexOf(i))return n.substring(i.length,n.length).replace(/\"/g,"");return null},t=e.write,c={"douban.com":1,"douban.fm":1,"google.com":1,"google.cn":1,"googleapis.com":1,"gmaptiles.co.kr":1,"gstatic.com":1,"gstatic.cn":1,"google-analytics.com":1,"googleadservices.com":1},i=function(e,o){var n=new Image;n.onload=function(){},n.src="https://www.douban.com/j/except_report?kind=ra022&reason="+encodeURIComponent(e)+"&environment="+encodeURIComponent(o)},r=function(o){try{t.call(e,o)}catc
with open("sample.html", "w+", encoding=r.encoding) as file:
file.write(r.text)from pathlib import Path
Path("sample.html").write_text(r.text)54094
r.encoding'utf-8'
import requests
r = requests.get('http://httpbin.org/get')
print(r.text){
"args": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.32.5",
"X-Amzn-Trace-Id": "Root=1-69071e97-17ab15150be5c81242c55bcd"
},
"origin": "129.227.235.26",
"url": "http://httpbin.org/get"
}
x = r.json()
x{'args': {},
'headers': {'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate',
'Host': 'httpbin.org',
'User-Agent': 'python-requests/2.32.5',
'X-Amzn-Trace-Id': 'Root=1-69071e97-17ab15150be5c81242c55bcd'},
'origin': '129.227.235.26',
'url': 'http://httpbin.org/get'}
x['url']'http://httpbin.org/get'
巨潮资讯网
科创板的公告:
http://www.cninfo.com.cn/new/commonUrl?url=disclosure/list/notice#sseKcp
import requests
import pandas as pd
url = "http://www.cninfo.com.cn/new/hisAnnouncement/query"
headers = {
'content-type': 'application/json',
'Accept-Encoding': 'gzip, deflate',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0'
}params = {'column': 'szse',
'pageNum': 1,
'plate': 'sz',
'searchkey': '辞职;', # key1 or key2
'pageSize': 30,
'seDate': '2023-10-01 ~ 2023-10-22',
'tabName': 'fulltext'}
r = requests.post(url, params=params, headers=headers)r.json(){'classifiedAnnouncements': None,
'totalSecurities': 0,
'totalAnnouncement': 95,
'totalRecordNum': 95,
'announcements': [{'id': None,
'secCode': '002939',
'secName': '长城证券',
'orgId': 'qsgn0000030',
'announcementId': '1218108610',
'announcementTitle': '关于公司董事辞职的公告',
'announcementTime': 1697817600000,
'adjunctUrl': 'finalpage/2023-10-21/1218108610.PDF',
'adjunctSize': 211,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '长城证券',
'shortTitle': '关于公司董事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '300801',
'secName': '泰和科技',
'orgId': '9900031706',
'announcementId': '1218108177',
'announcementTitle': '关于独立董事辞职暨补选独立董事的公告',
'announcementTime': 1697817600000,
'adjunctUrl': 'finalpage/2023-10-21/1218108177.PDF',
'adjunctSize': 210,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '泰和科技',
'shortTitle': '关于独立董事辞职暨补选独立董事的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '000420',
'secName': '吉林化纤',
'orgId': 'gssz0000420',
'announcementId': '1218108160',
'announcementTitle': '关于董事、董事会秘书辞职的公告',
'announcementTime': 1697817600000,
'adjunctUrl': 'finalpage/2023-10-21/1218108160.PDF',
'adjunctSize': 149,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '吉林化纤',
'shortTitle': '关于董事、董事会秘书辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '001965',
'secName': '招商公路',
'orgId': 'GD008065',
'announcementId': '1218108158',
'announcementTitle': '关于高级管理人员辞职的公告',
'announcementTime': 1697817600000,
'adjunctUrl': 'finalpage/2023-10-21/1218108158.PDF',
'adjunctSize': 105,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '招商公路',
'shortTitle': '关于高级管理人员辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '002075',
'secName': '沙钢股份',
'orgId': '9900001101',
'announcementId': '1218107902',
'announcementTitle': '关于职工代表监事辞职及补选的公告',
'announcementTime': 1697817600000,
'adjunctUrl': 'finalpage/2023-10-21/1218107902.PDF',
'adjunctSize': 77,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '沙钢股份',
'shortTitle': '关于职工代表监事辞职及补选的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '000532',
'secName': '华金资本',
'orgId': 'gssz0000532',
'announcementId': '1218106993',
'announcementTitle': '关于公司董事辞职的公告',
'announcementTime': 1697817600000,
'adjunctUrl': 'finalpage/2023-10-21/1218106993.PDF',
'adjunctSize': 119,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '华金资本',
'shortTitle': '关于公司董事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '300053',
'secName': '航宇微',
'orgId': '9900010151',
'announcementId': '1218109594',
'announcementTitle': '关于副总经理辞职的公告',
'announcementTime': 1697798665000,
'adjunctUrl': 'finalpage/2023-10-20/1218109594.PDF',
'adjunctSize': 99,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '航宇微',
'shortTitle': '关于副总经理辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '300650',
'secName': '太龙股份',
'orgId': '9900031733',
'announcementId': '1218109108',
'announcementTitle': '关于副总经理辞职的公告',
'announcementTime': 1697796629000,
'adjunctUrl': 'finalpage/2023-10-20/1218109108.PDF',
'adjunctSize': 62,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '太龙股份',
'shortTitle': '关于副总经理辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '002761',
'secName': '浙江建投',
'orgId': '9900023671',
'announcementId': '1218108414',
'announcementTitle': '中国国际金融股份有限公司关于浙江省建设投资集团股份有限公司董事长辞职暨推举董事代行董事长职责的重大受托管理事务临时报告',
'announcementTime': 1697793563000,
'adjunctUrl': 'finalpage/2023-10-20/1218108414.PDF',
'adjunctSize': 315,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010501||010112||013999',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '浙江建投',
'shortTitle': '中国国际金融股份有限公司关于浙江省建设投资集团股份有限公司董事长辞职暨推举董事代行董事长职责的重大受托管理事务临时报告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '002761',
'secName': '浙江建投',
'orgId': '9900023671',
'announcementId': '1218108413',
'announcementTitle': '招商证券股份有限公司关于浙江省建设投资集团股份有限公司董事长辞职暨推举董事代行董事长职责的临时受托管理事务报告(2)',
'announcementTime': 1697793434000,
'adjunctUrl': 'finalpage/2023-10-20/1218108413.PDF',
'adjunctSize': 397,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010501||010112||013999',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '浙江建投',
'shortTitle': '招商证券股份有限公司关于浙江省建设投资集团股份有限公司董事长辞职暨推举董事代行董事长职责的临时受托管理事务报告(2)',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '000584',
'secName': 'ST工智',
'orgId': 'gssz0000584',
'announcementId': '1218097684',
'announcementTitle': '关于公司监事辞职暨选举非职工监事的公告',
'announcementTime': 1697731200000,
'adjunctUrl': 'finalpage/2023-10-20/1218097684.PDF',
'adjunctSize': 221,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': 'ST工智',
'shortTitle': '关于公司监事辞职暨选举非职工监事的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '000826',
'secName': '启迪环境',
'orgId': 'gssz0000826',
'announcementId': '1218097065',
'announcementTitle': '关于公司高级管理人员辞职的公告',
'announcementTime': 1697731200000,
'adjunctUrl': 'finalpage/2023-10-20/1218097065.PDF',
'adjunctSize': 163,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '启迪环境',
'shortTitle': '关于公司高级管理人员辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '000030',
'secName': '富奥股份',
'orgId': 'gssz0000030',
'announcementId': '1218095768',
'announcementTitle': '关于公司董事辞职的公告',
'announcementTime': 1697731200000,
'adjunctUrl': 'finalpage/2023-10-20/1218095768.PDF',
'adjunctSize': 56,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '富奥股份',
'shortTitle': '关于公司董事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '000158',
'secName': '常山北明',
'orgId': 'gssz0000158',
'announcementId': '1218095021',
'announcementTitle': '关于职工代表监事辞职及补选职工代表监事的公告',
'announcementTime': 1697731200000,
'adjunctUrl': 'finalpage/2023-10-20/1218095021.PDF',
'adjunctSize': 165,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '常山北明',
'shortTitle': '关于职工代表监事辞职及补选职工代表监事的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '002238',
'secName': '天威视讯',
'orgId': '9900004647',
'announcementId': '1218094640',
'announcementTitle': '天威视讯关于高级管理人员辞职的公告',
'announcementTime': 1697731200000,
'adjunctUrl': 'finalpage/2023-10-20/1218094640.PDF',
'adjunctSize': 152,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '天威视讯',
'shortTitle': '天威视讯关于高级管理人员辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '300686',
'secName': '智动力',
'orgId': '9900029630',
'announcementId': '1218093982',
'announcementTitle': '2023-039 智动力:关于独立董事辞职的公告',
'announcementTime': 1697731200000,
'adjunctUrl': 'finalpage/2023-10-20/1218093982.PDF',
'adjunctSize': 68,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '智动力',
'shortTitle': '2023-039 智动力:关于独立董事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '300513',
'secName': '恒实科技',
'orgId': '9900026672',
'announcementId': '1218093902',
'announcementTitle': '关于独立董事辞职及提名独立董事候选人、监事辞职及补选监事的公告',
'announcementTime': 1697731200000,
'adjunctUrl': 'finalpage/2023-10-20/1218093902.PDF',
'adjunctSize': 100,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '恒实科技',
'shortTitle': '关于独立董事辞职及提名独立董事候选人、监事辞职及补选监事的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '002818',
'secName': '富森美',
'orgId': '9900026774',
'announcementId': '1218093786',
'announcementTitle': '关于职工代表监事辞职及补选职工代表监事的公告',
'announcementTime': 1697731200000,
'adjunctUrl': 'finalpage/2023-10-20/1218093786.PDF',
'adjunctSize': 233,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '富森美',
'shortTitle': '关于职工代表监事辞职及补选职工代表监事的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '301370',
'secName': '国科恒泰',
'orgId': '9900035522',
'announcementId': '1218093153',
'announcementTitle': '关于监事辞职的公告',
'announcementTime': 1697709263000,
'adjunctUrl': 'finalpage/2023-10-19/1218093153.PDF',
'adjunctSize': 105,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '国科恒泰',
'shortTitle': '关于监事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '300337',
'secName': '银邦股份',
'orgId': '9900022204',
'announcementId': '1218092322',
'announcementTitle': '银邦股份关于独立董事辞职的公告',
'announcementTime': 1697707584000,
'adjunctUrl': 'finalpage/2023-10-19/1218092322.PDF',
'adjunctSize': 87,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '银邦股份',
'shortTitle': '银邦股份关于独立董事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '300849',
'secName': '锦盛新材',
'orgId': '9900035518',
'announcementId': '1218094124',
'announcementTitle': '关于公司非独立董事辞职的公告',
'announcementTime': 1697707223000,
'adjunctUrl': 'finalpage/2023-10-19/1218094124.PDF',
'adjunctSize': 107,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '锦盛新材',
'shortTitle': '关于公司非独立董事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '002761',
'secName': '浙江建投',
'orgId': '9900023671',
'announcementId': '1218078979',
'announcementTitle': '浙江省建设投资集团股份有限公司关于公司董事长辞职暨推举董事代行董事长职责的公告',
'announcementTime': 1697644800000,
'adjunctUrl': 'finalpage/2023-10-19/1218078979.PDF',
'adjunctSize': 139,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '浙江建投',
'shortTitle': '浙江省建设投资集团股份有限公司关于公司董事长辞职暨推举董事代行董事长职责的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '001286',
'secName': '陕西能源',
'orgId': '9900048369',
'announcementId': '1218074786',
'announcementTitle': '陕西能源投资股份有限公司关于董事辞职的公告',
'announcementTime': 1697644800000,
'adjunctUrl': 'finalpage/2023-10-19/1218074786.PDF',
'adjunctSize': 150,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '陕西能源',
'shortTitle': '陕西能源投资股份有限公司关于董事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '300157',
'secName': '新锦动力',
'orgId': '9900015787',
'announcementId': '1218076867',
'announcementTitle': '关于公司董事辞职的公告',
'announcementTime': 1697622747000,
'adjunctUrl': 'finalpage/2023-10-18/1218076867.PDF',
'adjunctSize': 110,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '新锦动力',
'shortTitle': '关于公司董事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '002201',
'secName': '正威新材',
'orgId': '9900003944',
'announcementId': '1218072521',
'announcementTitle': '关于董事会秘书辞职的公告',
'announcementTime': 1697600664000,
'adjunctUrl': 'finalpage/2023-10-18/1218072521.PDF',
'adjunctSize': 144,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '正威新材',
'shortTitle': '关于董事会秘书辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '000755',
'secName': '山西路桥',
'orgId': 'gssz0000755',
'announcementId': '1218061388',
'announcementTitle': '关于独立董事辞职的公告',
'announcementTime': 1697558400000,
'adjunctUrl': 'finalpage/2023-10-18/1218061388.PDF',
'adjunctSize': 221,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '山西路桥',
'shortTitle': '关于独立董事辞职的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '301188',
'secName': '力诺特玻',
'orgId': 'gfbj0833017',
'announcementId': '1218059956',
'announcementTitle': '关于公司总经理、部分董事、董事长辞职暨聘任总经理、补选董事、选举董事长、调整专门委员会委员并变更法定代表人的公告',
'announcementTime': 1697558400000,
'adjunctUrl': 'finalpage/2023-10-18/1218059956.PDF',
'adjunctSize': 198,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012301||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '力诺特玻',
'shortTitle': '关于公司总经理、部分董事、董事长辞职暨聘任总经理、补选董事、选举董事长、调整专门委员会委员并变更法定代表人的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '301188',
'secName': '力诺特玻',
'orgId': 'gfbj0833017',
'announcementId': '1218059949',
'announcementTitle': '关于公司监事会主席辞职暨补选监事的公告',
'announcementTime': 1697558400000,
'adjunctUrl': 'finalpage/2023-10-18/1218059949.PDF',
'adjunctSize': 94,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||160203||250301||251302',
'pageColumn': 'SZCY',
'announcementType': '01010503||010112||010115||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '力诺特玻',
'shortTitle': '关于公司监事会主席辞职暨补选监事的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '002833',
'secName': '弘亚数控',
'orgId': '9900029438',
'announcementId': '1218059468',
'announcementTitle': '关于高级管理人员辞职暨聘任高级管理人员的公告',
'announcementTime': 1697558400000,
'adjunctUrl': 'finalpage/2023-10-18/1218059468.PDF',
'adjunctSize': 252,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '弘亚数控',
'shortTitle': '关于高级管理人员辞职暨聘任高级管理人员的公告',
'announcementTypeName': None,
'secNameList': None},
{'id': None,
'secCode': '002771',
'secName': '真视通',
'orgId': '9900023511',
'announcementId': '1218059327',
'announcementTitle': '关于公司副总经理辞职的公告',
'announcementTime': 1697558400000,
'adjunctUrl': 'finalpage/2023-10-18/1218059327.PDF',
'adjunctSize': 255,
'adjunctType': 'PDF',
'storageTime': None,
'columnId': '09020202||250101||251302',
'pageColumn': 'SZZB',
'announcementType': '01010503||010112||012303',
'associateAnnouncement': None,
'important': None,
'batchNum': None,
'announcementContent': '',
'orgName': None,
'tileSecName': '真视通',
'shortTitle': '关于公司副总经理辞职的公告',
'announcementTypeName': None,
'secNameList': None}],
'categoryList': None,
'hasMore': True,
'totalpages': 3}
content = r.json()
record = content["totalAnnouncement"]
record95
df = pd.DataFrame(content["announcements"])
df.head()| id | secCode | secName | orgId | announcementId | announcementTitle | announcementTime | adjunctUrl | adjunctSize | adjunctType | ... | announcementType | associateAnnouncement | important | batchNum | announcementContent | orgName | tileSecName | shortTitle | announcementTypeName | secNameList | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | None | 002939 | 长城证券 | qsgn0000030 | 1218108610 | 关于公司董事辞职的公告 | 1697817600000 | finalpage/2023-10-21/1218108610.PDF | 211 | ... | 01010503||010112||012303 | None | None | None | None | 长城证券 | 关于公司董事辞职的公告 | None | None | ||
| 1 | None | 300801 | 泰和科技 | 9900031706 | 1218108177 | 关于独立董事辞职暨补选独立董事的公告 | 1697817600000 | finalpage/2023-10-21/1218108177.PDF | 210 | ... | 01010503||010112||010115||012303 | None | None | None | None | 泰和科技 | 关于独立董事辞职暨补选独立董事的公告 | None | None | ||
| 2 | None | 000420 | 吉林化纤 | gssz0000420 | 1218108160 | 关于董事、董事会秘书辞职的公告 | 1697817600000 | finalpage/2023-10-21/1218108160.PDF | 149 | ... | 01010503||010112||012303 | None | None | None | None | 吉林化纤 | 关于董事、董事会秘书辞职的公告 | None | None | ||
| 3 | None | 001965 | 招商公路 | GD008065 | 1218108158 | 关于高级管理人员辞职的公告 | 1697817600000 | finalpage/2023-10-21/1218108158.PDF | 105 | ... | 01010503||010112||012303 | None | None | None | None | 招商公路 | 关于高级管理人员辞职的公告 | None | None | ||
| 4 | None | 002075 | 沙钢股份 | 9900001101 | 1218107902 | 关于职工代表监事辞职及补选的公告 | 1697817600000 | finalpage/2023-10-21/1218107902.PDF | 77 | ... | 01010503||010112||012303 | None | None | None | None | 沙钢股份 | 关于职工代表监事辞职及补选的公告 | None | None |
5 rows × 23 columns
http://static.cninfo.com.cn/finalpage/2021-11-22/1211672004.PDF
row = df.loc[0,:]
print(type(row))
row<class 'pandas.core.series.Series'>
id None
secCode 002939
secName 长城证券
orgId qsgn0000030
announcementId 1218108610
announcementTitle 关于公司董事辞职的公告
announcementTime 1697817600000
adjunctUrl finalpage/2023-10-21/1218108610.PDF
adjunctSize 211
adjunctType PDF
storageTime None
columnId 09020202||250101||251302
pageColumn SZZB
announcementType 01010503||010112||012303
associateAnnouncement None
important None
batchNum None
announcementContent
orgName None
tileSecName 长城证券
shortTitle 关于公司董事辞职的公告
announcementTypeName None
secNameList None
Name: 0, dtype: object
Path("pdf").mkdir(exist_ok=True)
pdf_url = f"http://static.cninfo.com.cn/{row.adjunctUrl}"
local_pdf = f'pdf/{row.secName}_{row.announcementId}.pdf'from urllib.request import urlretrieve
urlretrieve(pdf_url, local_pdf)
print(f"download from {pdf_url} to local file: {local_pdf}")download from http://static.cninfo.com.cn/finalpage/2023-10-21/1218108610.PDF to local file: pdf/长城证券_1218108610.pdf
9.3 模拟浏览器
Playwright 是一个强大的浏览器自动化工具,支持 Chromium、Firefox 和 WebKit。
在 Jupyter/Quarto 环境中,由于已存在 asyncio 事件循环,需要使用 异步 API。
在普通 Python 脚本中,可以使用同步 API。
异步 API 示例(Notebook 推荐)
from playwright.async_api import async_playwright
async def browse_example():
async with async_playwright() as p:
# 启动浏览器(headless=True 表示无界面模式)
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
# 访问网页
await page.goto("https://playwright.dev")
# 获取标题
title = await page.title()
print(f"页面标题: {title}")
# 截图
await page.screenshot(path="./data/playwright_example.png")
print("✓ 截图已保存")
# 关闭浏览器
await browser.close()
# 在 Jupyter 中直接运行异步函数
await browse_example()完整的网页自动化示例
from playwright.async_api import async_playwright
from pathlib import Path
async def search_and_save():
async with async_playwright() as p:
# 启动浏览器
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
# 访问百度首页
await page.goto('https://www.baidu.com')
print("✓ 已打开百度首页")
# 搜索"暨南大学管理学院"
await page.fill('input[name="wd"]', '暨南大学管理学院')
await page.click('input[type="submit"]')
print("✓ 已提交搜索")
# 等待搜索结果加载
await page.wait_for_load_state('networkidle')
# 获取页面标题
title = await page.title()
print(f"页面标题: {title}")
# 保存页面内容
html_content = await page.content()
Path('search_results.html').write_text(html_content, encoding='utf-8')
print("✓ 页面内容已保存到 search_results.html")
# 截图
await page.screenshot(path='search_screenshot.png')
print("✓ 截图已保存到 search_screenshot.png")
# 关闭浏览器
await browser.close()
# 运行
await search_and_save()同步 API(仅供参考,不在 Notebook 中使用)
以下代码仅适用于普通 Python 脚本,不要在 Jupyter Notebook 中运行。
# 仅在普通 .py 脚本中使用
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://playwright.dev")
print(page.title())
browser.close()9.4 解析HTML
import requests
headers = {'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 OPR/66.0.3515.115'}
url = 'https://book.douban.com/tag/小说?start=0&type=T'
resp = requests.get(url, headers = headers) #极少数情况会用到verify=False这参数
resp<Response [200]>
Path("sample.html").write_text(resp.text)53589
html_string = Path("sample.html").read_text() # local htmltype(html_string)str
常用的 HTML 解析库:
- Parsel - 支持 CSS 和 XPath,Scrapy 框架使用的解析库(推荐)
- lxml - 速度快,功能强大
- BeautifulSoup - 功能全面,语法简单
本教程使用 Parsel 库,演示两种选择器用法:CSS 和 XPath。
方法一:使用 Parsel + CSS 选择器
Parsel 是 Scrapy 框架使用的解析库,同时支持 CSS 选择器和 XPath。
from parsel import Selector
# 创建 Selector 对象
selector = Selector(text=html_string)
print(f"Selector 类型: {type(selector)}")Selector 类型: <class 'parsel.selector.Selector'>
CSS 选择器语法
为了定位html中对应的节点及其属性和含有的信息,我们需要使用选择器。
| CSS选择器 | 例子 | 解释 |
|---|---|---|
.class |
.intro |
选出class="intro"的节点 |
#id |
#firstname |
选出id="firstname"的节点 |
element |
p |
选出所有p标签的节点 |
element element |
div p |
选出div节点后代所有p节点 |
[attribute] |
[target] |
选出带有 target 属性所有节点 |
[attribute=value] |
[target=_blank] |
选出 target="_blank" 的所有节点 |
使用 Chrome 扩展 SelectorGadget 可以快速获取元素的 CSS 选择器。
Parsel CSS 选择器示例
# 选择所有书籍条目
items = selector.css('.subject-item')
print(f"找到 {len(items)} 个书籍条目")找到 20 个书籍条目
# 获取第一个书籍条目
first_item = selector.css('.subject-item')[0]
print(first_item)<li class="subject-item">
<div class="pic">
<a class="nbg" href="https://book.douban.com/subject/37339619/" onclick="moreurl(this,{i:'0',query:'',subject_id:'37339619',from:'book_subject_search'})">
<img class="" src="https://img2.doubanio.com/view/subject/s/public/s35162221.jpg" width="90">
</a>
</div>
<div class="info">
<h2 class="">
<a href="https://book.douban.com/subject/37339619/" title="桃花源没事儿" onclick="moreurl(this,{i:'0',query:'',subject_id:'37339619',from:'book_subject_search'})">
桃花源没事儿
</a>
</h2>
<div class="pub">
马伯庸 / 湖南文艺出版社 / 2025-6 / 48.00元
</div>
<div class="star clearfix">
<span class="allstar40"></span>
<span class="rating_nums">7.6</span>
<span class="pl">
(12983人评价)
</span>
</div>
<p>小道士玄穹是天生穷命,只要一捞横财,必有天雷劈下。他啥钱也不敢挣,只能去桃花源当个俗务道人。这项工作钱少活又多,担责加背锅,因为桃花源里住的都是妖怪,鸡毛蒜... </p>
<div class="ft">
<div class="collect-info">
</div>
<div class="cart-actions">
<span class="buy-info">
<a href="https://book.douban.com/subject/37339619/buylinks">
纸质版 35.00元
</a>
</span>
</div>
<div class="ebook-link">
<a target="_blank" href="https://read.douban.com/ebook/648940719/?dcs=tag-buylink&dcm=douban&dct=37339619">去看电子版</a>
</div>
</div>
</div>
</li>
# 遍历所有书籍,提取信息
for item in items[:3]: # 只看前3个
# 提取书名(使用 ::text 伪元素获取文本)
title = item.css('a[title]::attr(title)').get()
# 提取评分
rating = item.css('.rating_nums::text').get()
# 提取评价人数
pl = item.css('.pl::text').get()
print(f"书名: {title}")
print(f"评分: {rating}")
print(f"评价: {pl}")
print("-" * 50)书名: 桃花源没事儿
评分: 7.6
评价:
(12983人评价)
--------------------------------------------------
书名: 长安的荔枝
评分: 8.5
评价:
(261869人评价)
--------------------------------------------------
书名: 太白金星有点烦
评分: 9.0
评价:
(139370人评价)
--------------------------------------------------
# 提取图片URL
img_url = selector.css('.subject-item img::attr(src)').get()
print(f"图片URL: {img_url}")图片URL: https://img2.doubanio.com/view/subject/s/public/s35162221.jpg
# 提取所有书名(返回列表)
all_titles = selector.css('.subject-item a[title]::attr(title)').getall()
print(f"共找到 {len(all_titles)} 本书")
print(all_titles[:5]) # 显示前5本共找到 20 本书
['桃花源没事儿', '长安的荔枝', '太白金星有点烦', '食南之徒', '一句顶一万句']
方法二:使用 Parsel + XPath
XPath 是更强大的选择器语言,可以进行复杂的节点查询。
- CSS 选择器:简洁直观,适合简单查询
- XPath:功能更强大,支持复杂的逻辑判断和层级关系
推荐:简单场景用 CSS,复杂场景用 XPath。
XPath 基础语法
| XPath 表达式 | 说明 |
|---|---|
/ |
从根节点选取 |
// |
从任意位置选取 |
. |
当前节点 |
.. |
父节点 |
@ |
选取属性 |
text() |
选取文本内容 |
# 使用 XPath 选择所有书籍条目
items_xpath = selector.xpath('//li[@class="subject-item"]')
print(f"XPath 找到 {len(items_xpath)} 个书籍条目")XPath 找到 20 个书籍条目
# 使用 XPath 提取第一本书的信息
first_book = items_xpath[0]
# 提取书名
title_xpath = first_book.xpath('.//a[@title]/@title').get()
# 提取评分
rating_xpath = first_book.xpath('.//span[@class="rating_nums"]/text()').get()
# 提取出版信息
pub_xpath = first_book.xpath('.//div[@class="pub"]/text()').get()
print(f"书名: {title_xpath}")
print(f"评分: {rating_xpath}")
print(f"出版信息: {pub_xpath}")书名: 桃花源没事儿
评分: 7.6
出版信息:
马伯庸 / 湖南文艺出版社 / 2025-6 / 48.00元
# XPath 高级用法:条件筛选
# 选择评分大于等于 8.0 的书籍(需要将评分转为数字比较)
high_rated_books = []
for item in items_xpath:
rating = item.xpath('.//span[@class="rating_nums"]/text()').get()
title = item.xpath('.//a[@title]/@title').get()
if rating and float(rating) >= 8.0:
high_rated_books.append({
'title': title,
'rating': rating
})
print(f"评分 >= 8.0 的书籍共 {len(high_rated_books)} 本:")
for book in high_rated_books[:5]:
print(f" {book['title']}: {book['rating']}")评分 >= 8.0 的书籍共 18 本:
长安的荔枝: 8.5
太白金星有点烦: 9.0
食南之徒: 8.2
一句顶一万句: 9.0
素食者: 8.1
# XPath 提取所有图片 URL
img_urls_xpath = selector.xpath('//li[@class="subject-item"]//img/@src').getall()
print(f"共找到 {len(img_urls_xpath)} 个图片")
print(img_urls_xpath[:3])共找到 20 个图片
['https://img2.doubanio.com/view/subject/s/public/s35162221.jpg', 'https://img3.doubanio.com/view/subject/s/public/s34327482.jpg', 'https://img9.doubanio.com/view/subject/s/public/s34544956.jpg']
CSS vs XPath 对比示例
import pandas as pd
# 创建对比数据
comparison_data = []
for i, item in enumerate(items[:5], 1):
# CSS 方式
title_css = item.css('a[title]::attr(title)').get()
rating_css = item.css('.rating_nums::text').get()
# XPath 方式
title_xpath = items_xpath[i-1].xpath('.//a[@title]/@title').get()
rating_xpath = items_xpath[i-1].xpath('.//span[@class="rating_nums"]/text()').get()
comparison_data.append({
'序号': i,
'书名(CSS)': title_css,
'评分(CSS)': rating_css,
'书名(XPath)': title_xpath,
'评分(XPath)': rating_xpath,
'结果一致': title_css == title_xpath and rating_css == rating_xpath
})
df_comparison = pd.DataFrame(comparison_data)
print(df_comparison) 序号 书名(CSS) 评分(CSS) 书名(XPath) 评分(XPath) 结果一致
0 1 桃花源没事儿 7.6 桃花源没事儿 7.6 True
1 2 长安的荔枝 8.5 长安的荔枝 8.5 True
2 3 太白金星有点烦 9.0 太白金星有点烦 9.0 True
3 4 食南之徒 8.2 食南之徒 8.2 True
4 5 一句顶一万句 9.0 一句顶一万句 9.0 True
CSS 选择器方法: - selector.css('div') - 选择所有 div 元素 - selector.css('div::text').get() - 获取第一个 div 的文本 - selector.css('div::text').getall() - 获取所有 div 的文本 - selector.css('a::attr(href)').get() - 获取第一个 a 标签的 href 属性
XPath 方法: - selector.xpath('//div') - 选择所有 div 元素 - selector.xpath('//div/text()').get() - 获取第一个 div 的文本 - selector.xpath('//div/text()').getall() - 获取所有 div 的文本 - selector.xpath('//a/@href').get() - 获取第一个 a 标签的 href 属性
下载图片示例
url = selector.css('.subject-item img::attr(src)').get()
print(f"图片 URL: {url}")图片 URL: https://img2.doubanio.com/view/subject/s/public/s35162221.jpg
from urllib.request import urlretrieve
from pathlib import Path
# 设置本地文件名
local = f"{Path(url).name}"
print(f"保存路径: {local}")保存路径: s35162221.jpg
# 下载图片
urlretrieve(url, local)
print(f"✓ 图片已下载到: {local}")✓ 图片已下载到: s35162221.jpg
9.5 总结
本章介绍了网页爬虫的基础知识:
- HTTP 请求:使用
requests库获取网页内容 - HTML 解析:使用
parsel库的两种方式- CSS 选择器:简洁直观,适合大多数场景
- XPath:功能强大,适合复杂查询
- 浏览器自动化:使用
playwright处理 JavaScript 动态内容
- 遵守 robots.txt:检查网站的爬虫协议
- 控制请求频率:添加延时,避免给服务器造成压力
- 尊重版权:仅用于学习和研究,不用于商业用途
- 数据隐私:不爬取个人隐私信息
- Parsel 官方文档
- XPath 教程
- CSS 选择器参考
- Scrapy 框架(基于 Parsel 的强大爬虫框架)