一、什么是爬虫
二、基础
(一)获取网页源代码
库:urllib
from urllib.request import urlopen
url = 'http://www.baidu.com'
response = urlopen(url)
print(response.read().decode('utf-8'))
(二)网页加载方式
-
静态页面,全部加载;
-
动态网页,数据和页面分开加载和请求。
三、requests模块
(一)安装
pip install requests
(二)使用
import requests
url = "http://www.baidu.com"
response = requests.get(url)
response.encoding = "utf-8"
print(response.text)
(三)变量访问与伪装
import requests
content = input("请输入要搜索的内容:")
url=f"http://www.baidu.com/s?wd={content}"
# 请求头,模拟浏览器访问
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
response = requests.get(url,headers=headers)
print(response.text)
使用
headers自定义请求头部分,修改UA以模仿浏览器进行访问。
(四)post请求获取翻译信息
import requests
url = "https://fanyi.baidu.com/sug"
data = {
"kw":input("请输入要翻译的单词:")
}
response=requests.post(url,data=data)
print(response.json())# 返回的是json格式数据,所以使用json()方法
post在data段包含发送的数据,一般返回json格式的数据。
(五)get请求获取信息
import requests
url= "https://movie.douban.com/j/chart/top_list"
data = {
"type": 5,
"interval_id": "100:90",
"action": "",
"start": 0,
"limit": 20
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36"
}
response = requests.get(url, params=data, headers=headers)
print(response.json())
get请求时会将data段自动拼接到url后面。
(六)cookie处理
import requests
session = requests.Session()
url = "http://www.baidu.com"
data = {
"username": "username",
"password": "password"
}
response = session.post(url, data=data)
print(response.text)
创建一个Session对象,可以在同一个Session对象中保持cookie等信息,但使用requests时会开启一个新的对象,不会保留cookie。
除了session还可以在headers直接写入cookie字段。
(七)防盗链
在headers中加入refer,规避溯源反扒机制。
(八)代理使用
import requests
url = "http://www.baidu.com"
proxy = {
"http": "http://127.0.0.1:10809",
"https": "http://127.0.0.1:10809"
}
r = requests.get(url, proxies=proxy)
print(r.text)
使用代理可以减少服务器封ip地址的情况,但速度较慢,费用较贵。
四、数据处理
(一)正则表达式
1. 元字符
| 符号 | 含义 |
|---|---|
| . | 匹配除换行符以外的任意字符 |
| \w | 字母、数字、下划线 |
| \s | 空白符 |
| \d | 数字 |
| \n | 换行符 |
| \t | 制表符 |
| 字符串开头 | |
| $ | 字符串结尾 |
| \W | 非字母、非数字、非下划线 |
| \D | 非数字 |
| \S | 非空白符 |
| a|b | a或b |
| () | 分组,匹配括号内表达式 |
| [……] | 匹配字符串中的字符,如a-z |
| [^……] | 匹配除字符串中字符的所有字符 |
2.量词
| 符号 | 含义 |
|---|---|
| * | 重复0次或更多次数 |
| + | 重复1次或更多次数 |
| ? | 重复0次或1次 |
| {n} | 重复n次 |
| {n,} | 重复n次或更多次数 |
| {n,m} | 重复n到m次 |
| .* | 贪婪匹配,尽可能的匹配 |
| .*? | 惰性匹配,尽可能少匹配 |
3. 使用:re库
- 提取所有符合的内容
import re
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.findall(pattern, text)
print(result)
- 从迭代器获取所有符合的
import re
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.finditer(pattern, text)
for match in result:# 从迭代器中获取每一个匹配对象
print(match.group()) # 获取匹配的字符串
- 搜索第一个匹配的项
import re
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.search(pattern, text)
print(result.group()) # 获取匹配的字符串
- 从字符串开头开始匹配(如果第一个字符不符合则失败)
import re
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
pattern = r"\d{10}"
result = re.match(pattern, text)
print(result) # 获取匹配的字符串
- 预加载
import re
object = re.compile(r"\d{10}")
text = "我的电话号码是1234567890和1234567891,请不要告诉别人。"
result1 = object.findall(text)
result2 = object.finditer(text)
result3 = object.search(text)
print(result1)
print(result2)
print(result3.group())
- 提取多个内容
<>内写你取得名称来区别
import re
object = re.compile(r"<div class=\"(?P<class>.*?)\">(?P<content>.*?)</div>")
s = """
<div class="title">这是一个标题</div>
<div class="content">这是一个内容</div>
"""
result = object.finditer(s)
for item in result:
class_name = item.group("class")
content = item.group("content")
print(f"类名:{class_name},内容:{content}")
(二)使用BeautifulSoup处理
from bs4 import BeautifulSoup
html = """
<div class="title">这是一个标题</div>
<div class="content">这是一个内容</div>
"""
page = BeautifulSoup(html, "html.parser")
div = page.find("div", class_="title")
print(div.text)
divs = page.find_all("div")
classes = page.find_all("div", class_=True)
print(class_names)
for div in divs:
print(div.text)
print(div.get("class"))
find只找一个符合的。文本用
.text,图片等用.content
(三)xpath处理(lxml)
from lxml import etree
xml = """
<books>
<id>1</id>
<author>
<nickname>Neo</nickname>
<nickname>小王子</nickname>
<nickname class="1">Python爬虫基础</nickname>
</author>
<price>100</price>
<name>Python爬虫基础</name>
</books>
"""
tree = etree.XML(xml)
et = tree.xpath('//nickname/text()')
result = tree.xpath('//nickname')# 获取下级所有节点
for item in result:
print(item.text)
one = tree.xpath('//nickname[@class="1"]/text()')[0]
text() 获取节点内容,默认是列表,如果取出第一个元素,可以使用[0];
//表示所有节点,/表示根节点;
@class="1"获取属性为class的值为1的节点内容。
(四)pyquary
from pyquery import PyQuery as pq
html = """
<div>
<ul>
<li class="item-0">first item</li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-0 active"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1 active"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
"""
doc = pq(html)
a = doc("li")
b = doc("li a")
c = doc("li a span")
class_names = doc("li").attr("class")
item_text = doc("li").text()
print(class_names)
print(item_text)
print(a)
print(b)
print(c)
attr("class")获取第一个li标签的class属性值,多个标签会返回第一个标签的属性值,如果想获取所有标签的属性值,可以使用items()方法;
text()获取所有li标签的文本内容,多个标签会返回所有标签的文本内容;pyquery中就是直接用css选择器,注意返回的不是字符串,而是pyquery对象。
from pyquery import PyQuery as pq
html = """
<div class="title">这是一个标题</div>
<div class="content">这是一个内容</div>
"""
doc = pq(html)
after = doc(".title").after("<div class='after'>这是一个after标签</div>")
before = doc(".title").before("<div class='before'>这是一个before标签</div>")
print(doc)
remove = doc(".title").remove()
remove_attr = doc(".content").remove_attr("class")
print(doc)
add_class = doc(".after").add_class("new-class")
add_attr = doc(".after").attr("data-id", "123")
print(doc)
after()、before()在.title标签后面、前面添加一个新的标签;
remove()删除标签;
remove_attr("class")删除标签的class属性;
add_class("new-class")给标签添加一个新的class属性值;
attr("data-id", "123")给标签添加一个新的属性data-id,值为123,如果属性已经存在,会覆盖原有的属性值。
五、提高效率
(一)多线程
from threading import Thread
def task():
for i in range(5):
print(f"线程任务执行第 {i+1} 次")
thread = Thread(target=task) # 创建一个线程对象,target参数指定线程要执行的函数
thread.start() # 启动线程,线程开始执行任务
for i in range(5):
print(f"主线程执行第 {i+1} 次")
通过
Thread传递参数给函数时使用arg=()括号内必须为元组,如果只有一个变量,要在后面加逗号。
(二)多进程
from multiprocessing import Process
def task():
for i in range(5):
print(f"子进程任务执行第 {i+1} 次")
pro = Process(target=task) # 创建一个进程对象,target参数指定进程要执行的函数
pro.start() # 启动进程,进程开始执行任务
for i in range(5):
print(f"主进程执行第 {i+1} 次")
(三)线程池
from concurrent.futures import ThreadPoolExecutor
def task(n):
print(f"线程任务执行第 {n} 次")
with ThreadPoolExecutor(max_workers=5) as executor: # 创建一个线程池,max_workers参数指定线程池中线程的最大数量
for i in range(40):
executor.submit(task, i+1) # 提交任务到线程池,参数通过submit方法传递
print(f"主线程提交了第 {i+1} 个任务")
(四)协程
import asyncio
async def task(n):
for i in range(10):
print(f"异步任务执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1) # 模拟异步任务的耗时操作,可以使用asyncio.sleep()来实现非阻塞的睡眠
async def task2(n):
for i in range(10):
print(f"异步任务2执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
async def task3(n):
for i in range(10):
print(f"异步任务3执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
await asyncio.gather(task(1), task2(2), task3(3)) # 使用asyncio.gather()来并行执行多个异步任务
或者:
import asyncio
async def task(n):
for i in range(10):
print(f"异步任务执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1) # 模拟异步任务的耗时操作,可以使用asyncio.sleep()来实现非阻塞的睡眠
async def task2(n):
for i in range(10):
print(f"异步任务2执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
async def task3(n):
for i in range(10):
print(f"异步任务3执行第 {n} 次,第 {i+1} 次")
await asyncio.sleep(1)
async def main():
tasks = [asyncio.create_task(task(1)), asyncio.create_task(task2(2)), asyncio.create_task(task3(3))] # 创建一个包含多个异步任务的列表
await asyncio.wait(tasks) # 等待所有异步任务完成
if __name__ == "__main__":
asyncio.run(main()) # 运行主函数,启动事件循环,执行异步任务
aiohttp
import asyncio
import aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as session: # 创建一个异步HTTP会话
async with session.get(url) as response: # 发送GET请求,获取响应对象
return await response.text() # 获取响应内容,使用await等待异步操作完成
async def main():
url = "http://www.baidu.com"
content = await fetch(url) # 调用fetch函数,获取网页内容,使用await等待异步操作完成
print(content)
await main() # 在Jupyter Notebook中直接调用,不需要asyncio.run()