Python网络爬虫
第一章 web网站与访问
1.1 POST方法向网站发送数据、GET+POST混合方法向网站发送数据
GET | POST | GET+POST | |
---|---|---|---|
用途、语义上 | 信息获取(查询) | 资源请求(增删改) | 信息或资源的(增删改查) |
传参方式 | URL传值 | 表单传值(request body 传参) | URL传值+表单传值 |
服务器端获取数据方法 | args | form | (args+form)/values |
特点 | 适用于发送数据较小的场景;后退、刷新无害,速度快;书签可被收藏缓存,所有人可见,安全性差 | 适用于发送数据较大的场景;后退、刷新需要重新提交数据,速度慢;书签不可收藏、不能缓存,不可见,更安全 | 适用于所有场景,优缺点融合 |
1.2 Web下载文件
服务器向客户端发送文件名称和文件数据的过程
1.3 Web上传文件
客户端把文件发送到服务器(服务器接收数据的过程)
1.4 正则表达式
操作符 | 说明 | 实例 |
---|---|---|
. | 任何单个字符 | |
[] | 字符集 | [abc] 表示a、b、c,[a-z] 表示a到z单个字符 |
[^] | 非字符集(排除范围) | [^abc] 表示非a非b非c的单个字符 |
* | 前一个字符0次或无限次扩展 | abc* 表示ab、abc、abcc、abccc等 |
+ | 前一个字符1次获无限次扩展 | abc+ 表示abc、abcc、abccc等 |
? | 前一个字符0次或1次扩展 | abc? 表示ab、abc |
| | 左右表达式任意一个 | abc|def 表示abc、def |
{m} | 扩展前一个字符m次 | ab{2}c 表示abbc |
{m,n} | 扩展前一个字符m至n次(含n) | ab{1,2}c 表示abc、abbc |
^ | 匹配字符串开头 | ^abc 表示abc且在一个字符串的开头 |
$ | 匹配字符串结尾 | abc$ 表示abc且在一个字符串的结尾 |
() | 分组标记,内部只能使用| 操作符 | (abc) 表示abc,(abc|def) 表示abc、def |
\d | 数字,等价于[0-9] | |
\w | 单词字符,等价于[A-Za-z0-9_],A-Z和a-z以及数字和下划线 | |
\s | 匹配到一个空白字符(\n, \t, \r, 空格) |
1.4.1 正则表达式的使用
import re # python标准库,用于字符串匹配
reg = r"\d+" # 表示匹配连续的多个数值,在这里表示匹配连续多个数字
m = re.search(reg, "abc123cd")
print(m)
# 结果:
<_sre.SRE_Match object; span=(3,6), match='123'>
# 找到连续的数值:“123”,span(3,6)表示开始位置是3,结束位置是6
1.4.2 经典正则表达式
表达式 | 表示的字符串 |
---|---|
^[A-Za-z]+$ | 由26个字母组成的字符串 |
^[A-Za-z0-9]+$ | 由26个字母和数字组成的字符串 |
^-?\d+$ | 整数形式的字符串 |
^[0-9]*[1-9][0-9]*$ | 正整数形式的字符串 |
[1-9]\d{5} | 中国境内邮政编码,6位 |
\u4e00-\u9fa5 | 匹配中文字符 |
d{3}-\d{8}|\d{4}-\d{7} | 国内电话号码,010-73849573 |
1.4.3 Re库主要功能函数
函数 | 说明 |
---|---|
re.search() | 一个字符串中搜索匹配正则表达式的第一个位置,返回match对象 |
re.match() | 从一个字符串的开始位置起匹配正则表达式,返回match对象 |
re.findall() | 搜索字符串,以列表类型返回全部能匹配的子串 |
re.split() | 按照正则表达式匹配结果进行分割,返回列表类型 |
re.finditer() | 搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是match对象 |
re.sub() | 在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串 |
1.4.4 Re库另一种等价用法
import re
match = re.search(r'[1-9]\d{5}','BIT100081')
等价于:
pat = re.compile(r'[1-9\d{5}]') # 编译为正则表达式对象
match = pat.search('BIT100081')
第二章web网页数据爬取方法BeautifulSoup
from bs4 import BeautifulSoup
soup = BeautifulSoup('<p>data<\p>', 'html.parser')
bs4库的prettity() 方法,soup.prettity()
补全html文档
2.1 BeautifulSoup查找文档元素
- 基本元素:
- .name,获取标签名称
- .attrs,获取标签内所有属性,返回一个字典
- .string,获取标签的文本信息,如果标签内包含了多个子节点并有多个文本时返回None
2.2 BeautifulSoup遍历文档元素
标签类型 | 标签迭代类型 |
---|---|
.contents, .parent, .next_sibling, .previous_sibling | .children, .parents, .descendants, .next_siblings, .previous_siblings |
# 获取父·祖先节点
print(soup.p.parent) # 获取p标签的直接父节点
print(soup.p.parents) # 获取p标签的祖先节点,返回一个生成器
# 获取子·子孙节点
print(soup.p.contents) # 获取p标签内的直接子节点,返回一个列表
print(soup.p.children) # 获取p标签内的直接子节点,返回一个生成器
print(soup.p.descendants) # 获取p标签内的子孙节点,返回一个生成器
# 获取兄弟节点
print(soup.a.previous_sibling) # 获取a标签的上一个兄弟节点
print(soup.a.previous_siblings) # 获取a标签前面的所有兄弟节点,返回一个生成器
print(soup.a.next_sibling) # 获取a标签的下一个兄弟节点
print(soup.a.next_siblings) # 获取a标签后面的所有兄弟节点,返回一个生成器
2.2.1 使用方法选择器
- find() 返回匹配到的第一个结果
- find_all() 返回一个包含所有匹配结果的列表
print(soup.find_all(text=re.compile('Lacie'), limit=2)) # 使用正则获取所有文本包含'Lacie'的节点(limit: 限制匹配个数)
print(soup.find_all('a', text='Lacie')) # 获取所有a标签内文本等于'Lacie'的节点(文本完整匹配)
print(soup.find_all('a', id='link2')) # 获取所有a标签内id等于'link2'的节点
print(soup.find_all('a', class_='sister')) # 获取所有a标签内class等于'sister'的节点
print(soup.find_all('a', class_='sister', id='link2')) # 多个搜索条件叠加
print(soup.find_all(name='a')) # 获取所有a节点
print(soup.find_all(attrs={'class': 'sister'})) # 获取所有class属性值为'sister'的节点
2.3 使用CSS语法查找元素
2.3.1 使用CSS语法
- 调用规则:
tag.select(css)
- 一般结构:
[tagName][attName[=value]]
2.3.2 语法规则
选择器 | 描述 |
---|---|
[attName] | 选取带有指定属性的字符串 |
[attName=value] | 选取带有指定属性和值的字符串 |
[attName^=value] | 匹配属性值以指定值开头的每个元素 |
[attName$=value] | 匹配属性值以指定值结尾的每个元素 |
[attName*=value] | 匹配属性值包含指定值的每个元素 |
2.3.3 select查找
select查找子孙节点 | select查找直接子节点 | select查找兄弟节点 |
---|---|---|
div p | div>p | div~p,后续所有同级别的节点;div+p,后续第一个同级别的兄弟节点 |
print(soup.select('p')) # 获取所有p标签,返回一个列表
print(soup.select('p a')) # 获取所有p标签内的a节点,返回一个列表
print(soup.select('p.story')) # 获取p标签内class为'story'的所有元素,返回一个列表
print(soup.select('.story')) # 获取class为'story'的所有元素,返回一个列表
print(soup.select('.beautiful.title')) # 获取class为'beautiful title'的所有元素,返回一个列表
print(soup.select('#link1')) # 获取id为'link1'的所有元素,返回一个列表
第三章 网站数据爬取路径
3.1 网站树的爬取路径
- 递归程序实现
- 分治、直接间接调用自身
- 深度优先遍历(DFS)
- 后进先出,栈,list
- 广度优先遍历(BFS)
- 先进先出,队列,list
递归分治:一棵树的遍历划分为根结点和它子树们的遍历
3.2 网站图的爬取路径
如果每个网页都能回到主界面,那么网站的关系就是一个有回路的图
3.3 python的前后台线程
setDaemon(True)
:设置后台线程,守护线程- 主线程退出时后台线程随机退出,优先级较低,用于为其他线程提供服务
- 线程的等待:线程对象.join(),该线程完成后才能继续往下运行
- 多线程与资源:
lock = threading.RLock() lock.acquire() # 获取锁 ...... lock.release() # 释放锁
setDaemon(False)(默认情况)
:非守护线程,也称为前台线程- 主线程退出时,若前台线程还未结束,则等待所有前台线程结束,相当于在程序末尾加入join()
- 多线程与资源(前后台线程)
第四章 Scrapy框架爬虫程序
4.1 Scrapy爬虫框架介绍
4.1.1 Scrapy爬虫框架结构
5+2结构,分布式,3条数据流路径
模块 | 功能 |
---|---|
Engine | 控制各模块数据流,不间断从Scheduler处获得爬取请求,直至请求为空 |
Spider | 框架入口(初始爬取请求)。解析Downloader返回的响应(Response),产生爬取项(scraped item),产生额外的爬取请求(Request) |
Item Pipelines | 框架出口(爬取结果及处理)以流水线方式处理Spider产生的爬取项 |
Scheduler | 对所有爬取请求进行调度管理 |
Spider Middleware | 对请求和爬取项的再处理(修改、丢弃、新增请求或爬取项) |
Downloader | 根据请求下载网页 |
Downloader Middleware | 实施Engine、Scheduler和Downloader之间进行用户可配置的控制(修改、丢弃、新增请求或响应) |
4.1.2 Scrapy命令行的使用
>scrapy <command> [options] [args]
命令 | 说明 | 格式 |
---|---|---|
startproject | 创建一个新工程 | scrapy startproject <name> [dir] |
genspider | 创建一个爬虫 | scrapy genspider [options] <name> <domain> |
settings | 获得爬虫配置信息 | scrapy settings [options] |
crawl | 运行一个爬虫 | scrapy crawl <spider> |
list | 列出工程中所有爬虫 | scrapy list |
shell | 启动URL调试命令行 | scrapy shell [url] |
4.1.3 scrapy与request对比
request | Scrapy | |
---|---|---|
相同点 | 1.实现python爬虫重要技术路线 2.可用性都好,文档丰富,入门简单 | 3.两者都没有处理js、提交表单、应对验证码等功能(可扩展) |
不同点 | 页面级爬虫 | 网站级爬虫 |
功能库 | 框架 | |
并发性考虑不足,性能较差 | 并发性好,性能较高 | |
重点在于页面下载 | 重点在于爬虫结构 | |
定制灵活 | 一般定制灵活,深度定制困难 | |
上手十分简单 | 入门稍难 |
4.1.4 yield关键字和生成器
生成器相比一次列出所有内容的优势:
- 更节省存储空间
- 响应更迅速
- 使用更灵活
4.1.5 Scrapy爬虫的数据类型
4.1.5.1 Request类
Request对象表示一个HTTP请求,由Spider生成,由Downloader执行
属性或方法 | 说明 |
---|---|
.url | Request对应的请求URL地址 |
.method | 对应的请求方法,GET POST 等 |
.headers | 字典类型风格的请求头 |
.body | 请求内容主体,字符串类型 |
.meta | 用户添加的扩展信息,在Scrapy内部模块间传递信息使用 |
.copy() | 复制该请求 |
4.1.5.2 Response类
Response对象表示一个HTTP响应,由Downloader生成,由Spider处理
属性或方法 | 说明 |
---|---|
.url | Response对应的URL地址 |
.status | HTTP状态码,默认是200 |
.headers | Response对应的头部信息 |
.body | Response对应的内容信息,字符串类型 |
.flags | 一组标记 |
.request | 产生Response类型对应的Request对象 |
.copy() | 复制该响应 |
4.1.5.3 Item类
Item对象表示一个从HTML页面中提取的信息内容,由Spider生成,由Item Pipeline处理Item,类似字典类型,可以按照字典类型操作
4.2 Scrapy中查找HTML元素Xpath
路径表达式+索引+属性
xpath的索引值从1开始
4.2.1 Xpath常用表达式
表达式 | 描述 | 例子 |
---|---|---|
nodename | 选取此节点 | body ,选取body元素 |
/ | 绝对路径,表示当前节点的下一级节点元素 | /body ,当前节点下一级的body元素,默认当前节点选取根元素 |
// | 相对路径,全文档查找 | //title ,全文档搜索title元素;body//title ,在body元素后代中搜索所有title元素 |
. | 当前节点 | .//title ,在当前节点后代中搜索所有title元素 |
@ | 选取属性 | //node[@attribute] ,包含attribute属性的节点node |
* | 通配符 | /* ,绝对路径匹配任意节点;//* ,全文匹配任意节点;@* ,匹配任意属性 |
4.3 Scrapy爬取和存储数据
总结:
- scrapy把数据爬取与数据存储分开处理,他们都是异步执行的,mySpider每爬取一个数据项目item,就yield推送给pipelines.py程序存储;
- 等待存储完毕后又再次爬取item再次推送再次存储
- 以上过程一直进行直到爬取过程结束
第五章 爬取网站商品数据
Selenium脚本执行时后端六条实现的流程:
- 对于每一条Selenium脚本,一个http请求会被创建并且发送给浏览器的驱动
- 浏览器驱动中包含了一个HTTP Server,用来接收这些http请求
- HTTP Server接收到请求后根据请求来具体操控对应的浏览器
- 浏览器执行具体的测试步骤
- 浏览器将步骤执行结果返回给HTTP Server
- HTTP Server又将结果返回给Selenium的脚本,如果是错误的http代码我们就会在控制台看到对应的报错信息
5.1 Selenium编写爬虫程序
- 程序先从selenium引入webdriver,引入chrome程序的选择项目Options:
from selenium import webdriver from selenium.webdriver.chrome.options import Options
- 设置启动chrome时不可见
chrome_options.add_argument('-headless') chrome_options.add_argument('-disable-gpu')
- 创建chrome浏览器
driver = webdriver.Chrome(chrome_options=chrome_options)
- 这样创建的chrome浏览器是不可见的,仅仅使用:
driver = webdriver.Chrome()
- 创建chrome浏览器,那么在程序执行时会弹出一个chrome浏览器
- 这样创建的chrome浏览器是不可见的,仅仅使用:
- 使用driver.get(url)方法访问网页
driver.get("http://127.0.0.1:5000")
- 通过driver.page_source获取网页HTML代码(渲染后的数据页面)
html = driver.page_source
- driver.get_cookies():获得页面的cookies
- driver.current_url:查看请求的URL
5.2 Selenium爬取Ajax网页数据
Ajax网页数据:后台与服务器进行少量数据交换,异步更新部分网页
模拟浏览器对下拉选项的点击选择,获得动态交互加载出更新后的网页数据
5.3 Selenium等待HTML元素
为什么要等待html元素?
HTML元素还没加载完毕,无法获得该元素的控制和访问
- 强制等待1.5秒
time.sleep(1.5)
- 隐性等待1.5秒,即网页在加载时等待1.5秒
driver.implicitly_wait(1.5)
- 循环等待
waitTime = 0 while waitTime < 10: marks = driver.find_element(By.XPATH, '//select/option') if len(marks) > 0: break time.sleep(0.5) waitTime += 0.5 if waitTime >= 10: raise Exception("Waiting time out")
- 显式等待
locator = (By.XPATH, "//select/option") WebDriverWait(driver, 10, 0.5).until(EC.presence_of_element_located(locator)) # 等待判断准确,不会浪费多余的等待时间,在实际使用中可以提高执行效率
5.3.1 等待方法
- EC.presence_of_element_located(locator)
- 等待locator指定的元素出现,也就是HTML文档中建立起了这个元素
- EC.visibility_of_element_located(locator)
- 等待locator指定的元素可见
- EC.element_to_be_clickable(locator)
- 等待locator指定的元素可以被点击
- EC.element_to_be_selected(locator)
- 等待locator指定的元素可以被选择
- 可以被选择的元素一般是
<select>
中的选项<option>
、输入的多选框<input type="checkbox">
、输入的单选框<input type="radio">
- EC.text_to_be_present_in_element(locator, text)
- 等待locator指定的元素的文本中包含指定的text文本
locator = (By.ID, "msg") WebDriverWait(driver, 10, 0,5).until(EC.text_to_be_present_in_element(locator, "品")) # 等待<span id="msg">...</span>元素中的文本包含“品”, # 由于在<option>出现后设置文本时“品牌”,因此爬虫程序可以爬取到手机品牌数据
第七章 读取文档
7.1 文档编码
- 共同点:所有信息和文档都是由0和1编码而成
- 不同点:面向用户的转换方式不同
完整代码:
import urllib
from urllib.request import urlopen
headers = {
"User-Agent": "Mozilla/5.0 (Windows; U; Windows NT 6.0 x64; en-US; rv:1.9pre) Gecko/2008072421 Minefield/3.0.2pre"
}
req = urllib.request.Request("http://www.pythonscraping.com/pages/warandpeace/chapter1-ru.txt", headers=headers)
resp = urllib.request.urlopen(req, timeout=10)
html = resp.read()
rutxt = html.decode('utf-8')
print(rutxt)
检索该文本文档的编码方法
import chardet
type = chardet.detect(html)
rutxt = html.decode(type["encoding"])
网页中有注明编码方式,查询
<meta>
标签
<meta charset="utf-8">
7.2 不同格式文档的读取和存储
CSV | DOCX | ||
---|---|---|---|
格式特点 | 以纯文本形式存储表格数据(数字和文本) | 以PostScript语言图像模型为基础,是跨平台(应用程序、操作系统、硬件无关)支持多媒体集成文件 | 类XML格式标准 |
读取 | csv.Reader()、csv.DictReader()方法,和引用io里面的StringIO() | 安装pdfminer3k | from zipfile import ZipFile、from io import BytesIO、from bs4 import BeautitulSoup |
存储 | csv.writer() | 不关注内容的转存,f.write(data) |
7.3 PDF读取和存储
思路:PDF读成字符串,然后用StingIO转换成文件对象
# 获取文档对象
fp = open("selenium_documentation_o.pdf", "rb")
# 创建一个与文档关联的解释器
parser = PDFParser(fp)
# PDF文档的对象
doc = PDFDocument()
# 链接解释器和文档对象
parser.set_document(doc)
doc.set_parser(parser)
# 初始化文档
doc.initialize("")
7.3.1 读取pdf
import urllib
from urllib.request import urlopen
from io import StringIO
from pdfminer.pdfinterp import PDFResourceManager, process_pdf
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams
from io import open
def readPDF(pdfFile):
rsrcmgr = PDFReasourceManager() # 创建PDF资源管理对象
retstr = StringIO() # StingIO转换成文件对象
laparams = LAParams() # 布局参数分析器
device = TextConverter(rsrcmgr, retstr, laparams=laparams) # 创建设备对象
process_pdf(rsrcmgr, device, pdfFile) # PDF页面解释器,等同于PDFPage.get_pages
device.close()
content = retstr.getvalue()
retstr.close()
return content
7.3.1.1下载服务器pdf文件
req = urllib.request.Request("http://pythonscraping.com/pages/warandpeace/chapter1.pdf",headers=headers)
pdfFile = urllib.request.urlopen(req, timeout=10)
outputString = readPDF(pdfFile)
print(outputString)
pdfFile.close()
7.3.1.2 打开本地PDF文件
pdfFile = open("F:\First Home work.pdf", "rb")
outputString = readPDF(pdfFile)
print(outputString)
pdfFile.close()
7.3.2 PDF文件的存储
req = urllib.request.Request("http://pythonscraping.com/pages/warandpeace/chapter1.pdf", headers=headers)
pdfFile = urllib.request.urlopen(req, timeout=10)
data = pdfFile.read()
# 存储PDF文件
pdfStoreFile = "F:\warandpeace_chapter1.pdf"
with open(pdfStoreFile, 'wb+') as f:
f.write(data)
7.3.3 网页信息转存为pdf
from fpdf import FPDF
from fpdf import HTMLMixin
class HTML2PDF(FPDF,HTMLMixin):
pass
def html2pdf():
html = '''<h1 align ="center">PyFPDF HTML Demo</h1>
<p>This is regular text</p>
<p>You can also <b>bold</b>, <i>italicize</i> or <u>underline</u></p>
'''
pdf = HTML2PDF()
pdf.add_page() # 加一页
pdf.write_html(html)
pdf.output('html2pdf.pdf')
html2pdf()
7.4 读取.docx文件的正文内容
- 从.docx文件读取XML
- 从XML中找到包含在
<w:t>
标签的正文的信息
from zipfile import ZipFile
from urllib.request import urlopen
from io import BytesIO
from bs4 import BeautifulSoup
wordFile = urlopen("http://pythonscraping.com/pages/AWordDocument.docx").read()
wordFile = BytesIO(workFile)
xml_content = document.read("word/document.xml") # 正文内容隐藏在xml里
print(xml_content.decode('utf-8'))
# <w:t>非标准标签,需要解析器xml解析
wordObj = BeautifulSoup(xml_content.decode('utf-8'), "xml")
textString = wordObj.findAll("w:t") # 找到包含在<w:t>标签的正文
for textElem in textString:
style = textElem.parent.parent.find('w:pStyle')
if style is not None and style['w:val']=="Title":
print("Title is :{}".format(textElem.text)) # 按不同要求单独打印标题
else:
print(textElem.txt)