Python网络爬虫基础进阶到实战教程

文章目录

  • 认识网络爬虫
  • HTML页面组成
  • Requests模块get请求与实战
      • 效果图
      • 代码解析
  • Post请求与实战
        • 代码解析
      • 发送JSON格式的POST请求
      • 使用代理服务器发送POST请求
      • 发送带文件的POST请求
  • Xpath解析
        • XPath语法的规则集:
        • XPath解析的代码案例及其详细讲解:
          • 使用XPath解析HTML文档
          • 使用XPath解析XML文档
          • 处理命名空间的XPath解析
  • BeautifulSoup详讲与实战
      • 创建BeautifulSoup对象
        • 遍历文档树
        • 搜索文档树
        • 获取节点属性和文本内容
      • BeautifulSoup 代码案例讲解。
        • 解析HTML文档并获取标题
      • 遍历文档树并获取所有段落内容
      • 使用CSS选择器搜索文档树
      • 使用正则表达式搜索文档树
      • 解析XML文档并获取节点信息
      • 修改节点属性
  • 正则表达式
      • 正则表达式知识点
      • 正则表达式案例
  • 正则表达式实战
  • 字体反爬
  • Scrapy入门
      • Scrapy实例
  • 完结

认识网络爬虫

网络爬虫是指一种程序自动获取网页信息的方式,它能够自动化地获取互联网上的数据。通过使用网络爬虫,我们可以方便地获取到网络上的各种数据,例如网页链接、文本、图片、音频、视频等等。

HTML页面组成

网页是由HTML标签和内容组成,HTML标签通过标签属性可以定位到需要的内容。网页中的样式由CSS控制,JavaScript可以实现网页动态效果。

HTML标签是一种用于构建Web页面的标记语言,它描述了页面的结构和元素。HTML标签通常包含一个起始标签和一个结束标签,例如<div></div>。HTML标签也可以包含属性,属性用于提供有关元素的额外信息。例如,<a>元素的href属性指定了链接目标的URL地址,而<img>元素的src属性指定了要显示的图像文件的URL地址。

CSS是一种用于控制Web页面样式的样式表语言,它可以为HTML元素提供样式和布局。通过CSS,我们可以控制文本的字体、颜色、大小和样式,以及元素的大小、位置、边框和背景等。

JavaScript是用于实现Web页面动态效果的一种编程语言,它可以实现网页上的各种交互效果,例如弹出窗口、表单验证、动画效果等。

Requests模块get请求与实战

Requests是Python中的HTTP库,提供了简洁易用的接口进行HTTP请求。其中,GET请求常用于获取静态网页信息。

我们通过requests.get()方法来发送一个GET请求.

import requestsurl = 'https://www.baidu.com'
response = requests.get(url)
print(response.text)

效果图

代码解析

第一行导入了requests模块,第二行指定了要请求的URL地址,在本例中我们使用百度首页作为示例。第三行使用requests库的get()方法来获取该URL的响应对象。响应对象包含了服务器返回的所有信息,包括Header(头部)和Body(主体)两部分。其中Header包含了很多信息,如日期、内容类型、服务器版本等,而Body包含了页面HTML源代码等具体信息。

第四行使用print()函数打印出响应内容的文本形式。运行这段代码,我们就可以在终端中看到百度首页的HTML源代码。

在实际爬虫中,我们可以利用requests模块的一些属性或者方法来解析响应内容,提取需要的数据。例如,可以使用response.status_code属性来获取HTTP状态码,使用response.headers属性来获取HTTP头部信息等。此外,我们还可以使用response.json()方法来解析JSON格式的响应内容,使用response.content方法来获取字节形式的响应内容等。

Post请求与实战

POST请求与GET请求的区别在于,POST请求会将请求参数放在请求体中,而GET请求则将请求参数放在URL中。通常情况下,POST请求比GET请求更安全,因为它可以隐藏请求参数。

我们通过requests.post()方法来发送一个POST请求,下面是详细的代码分析:

import requestsurl = 'http://xxxx.org/post'  # 这里使用xxxx.org来演示POST请求
data = {'key1': 'value1', 'key2': 'value2'}
response = requests.post(url, data=data)
print(response.text)

代码解析

第一行导入了requests模块,
第二行指定了要请求的URL地址 。
第三行定义了请求参数data,这个字典中包含了两个键值对,分别表示key1和key2这两个参数的值。第四行使用requests库的post()方法来发送POST请求并获取响应对象。

在实际爬虫中,我们可以利用requests模块的一些属性或者方法来解析响应内容,提取需要的数据。

发送JSON格式的POST请求

import requests
import jsonurl = 'http://xxxx.org/post'  # 这里使用xxxx.org来演示POST请求
data = {'key1': 'value1', 'key2': 'value2'}
headers = {'Content-Type': 'application/json'}
response = requests.post(url, data=json.dumps(data), headers=headers)
print(response.text)

在这个案例中,我们将请求参数data转换成JSON格式,并使用headers来指定Content-Type为application/json。然后,我们通过requests库的post()方法来发送POST请求。

使用代理服务器发送POST请求

import requestsproxy = {'http': 'http://10.10.1.10:3128','https': 'https://10.10.1.10:1080'
}
url = 'http://xxxx.org/post'  # 这里使用xxxx.org来演示POST请求
data = {'key1': 'value1', 'key2': 'value2'}
response = requests.post(url, data=data, proxies=proxy)
print(response.text)

发送带文件的POST请求

import requestsurl = 'http://xxxx.org/post'  # 这里使用xxxx.org来演示POST请求
files = {'file': open('myfile.txt', 'rb')}
response = requests.post(url, files=files)
print(response.text)

Xpath解析

XPath语法的规则集:

表达式描述
nodename选择所有名为nodename的元素
/从当前节点选取根节点
//从当前节点选取任意节点
.选择当前节点
选择当前节点的父节点
@选择属性
*匹配任何元素节点
[@attrib]选择具有给定属性的所有元素
[@attrib=‘value’]选择具有给定属性值的所有元素
tagname[text() = ‘text’]选择具有给定文本的所有tagname元素

XPath解析的代码案例及其详细讲解:

使用XPath解析HTML文档
from lxml import etree
import requestsurl = 'https://www.baidu.com'
html = requests.get(url).text
selector = etree.HTML(html)
result = selector.xpath('//title/text()')
print(result[0])
使用XPath解析XML文档
from lxml import etreexml = '''
<bookstore><book category="cooking"><title lang="en">Everyday Italian</title><author>Giammarco Tomaselli</author><year>2010</year><price>30.00</price></book><book category="children"><title lang="en">Harry Potter</title><author>J.K. Rowling</author><year>2005</year><price>29.99</price></book>
</bookstore>
'''selector = etree.XML(xml)
result = selector.xpath('//book[1]/title/text()')
print(result[0])
处理命名空间的XPath解析
from lxml import etreexml = '''
<ns:bookstore xmlns:ns="http://www.example.com"><ns:book category="cooking"><ns:title lang="en">Everyday Italian</ns:title><ns:author>Giammarco Tomaselli</ns:author><ns:year>2010</ns:year><ns:price>30.00</ns:price></ns:book><ns:book category="children"><ns:title lang="en">Harry Potter</ns:title><ns:author>J.K. Rowling</ns:author><ns:year>2005</ns:year><ns:price>29.99</ns:price></ns:book>
</ns:bookstore>
'''selector = etree.XML(xml)
ns = {'ns': 'http://www.example.com'}
result = selector.xpath('//ns:book[1]/ns:title/text()', namespaces=ns)
print(result[0])

BeautifulSoup详讲与实战

创建BeautifulSoup对象

首先我们需要导入BeautifulSoup模块:

from bs4 import BeautifulSoup

使用BeautifulSoup对HTML文档进行解析,可以通过以下两种方式:

(1) 传递一个HTML字符串作为参数:

html_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')

(2) 传递一个文件路径或文件对象作为参数:

with open('example.html', 'r') as f:soup = BeautifulSoup(f, 'html.parser')

遍历文档树

(1) .contents:返回一个包含所有子节点的列表。

for child in soup.body.contents:print(child)

(2) .children:返回一个包含所有子节点的迭代器。

for child in soup.body.children:print(child)

(3) .descendants:返回一个包含文档树中所有子孙节点的迭代器。

for element in soup.descendants:print(element)

(4) .parent:返回一个节点的父节点。

p = soup.body.p
print(p.parent)

(5) .parents:返回一个包含节点所有祖先节点的迭代器。

p = soup.body.p
for parent in p.parents:print(parent.name)

搜索文档树

(1) .find_all():返回一个满足条件的节点列表。

soup.find_all('p', class_='para1')
soup.find_all('p', {'class': 'para1'}, string='第一段落')

(2) .find():返回第一个满足条件的节点。

soup.find('p', class_='para1')
soup.find('p', {'class': 'para1'}, string='第一段落')

(3) .select():使用CSS选择器语法返回满足条件的节点列表。

soup.select('p.para1')
soup.select('p[class="para1"]')

获取节点属性和文本内容

(1) .get():获取节点的指定属性。

p = soup.find('p', class_='para1')
print(p.get('class'))

(2) .text:获取节点的文本内容。

p = soup.find('p', class_='para1')
print(p.text)

(3) .string:获取节点的文本内容(如果节点只有一个子节点且该子节点是字符串类型)。

p = soup.find('p', class_='para1')
print(p.string)

BeautifulSoup 代码案例讲解。

解析HTML文档并获取标题

from bs4 import BeautifulSoup
import requestsurl = 'https://www.baidu.com'
html = requests.get(url).text
soup = BeautifulSoup(html, 'html.parser')
title = soup.title.string
print(title)

遍历文档树并获取所有段落内容

from bs4 import BeautifulSouphtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')
for p in soup.body.children:if p.name == 'p':print(p.string)

使用CSS选择器搜索文档树

from bs4 import BeautifulSouphtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')
p_list = soup.select('p.para1')
for p in p_list:print(p.text)

使用正则表达式搜索文档树

import re
from bs4 import BeautifulSouphtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')
pattern = re.compile('^p.*?1$')  # 匹配所有以p开头并且以1结尾的类名
p_list = soup.find_all(class_=pattern)
for p in p_list:print(p.text)

解析XML文档并获取节点信息

from bs4 import BeautifulSoupxml_doc = """
<?xml version="1.0" encoding="UTF-8"?>
<data><country name="Liechtenstein"><rank>1</rank><year>2008</year><gdppc>141100</gdppc><neighbor name="Austria" direction="E"/><neighbor name="Switzerland" direction="W"/></country><country name="Singapore"><rank>4</rank><year>2011</year><gdppc>59900</gdppc><neighbor name="Malaysia" direction="N"/></country>
</data>
"""soup = BeautifulSoup(xml_doc, 'xml')
for country in soup.find_all('country'):print(country['name'])print(country.rank.text)print(country.year.text)print(country.gdppc.text)for neighbor in country.find_all('neighbor'):print(neighbor['name'], neighbor['direction'])

修改节点属性

from bs4 import BeautifulSouphtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""soup = BeautifulSoup(html_doc, 'html.parser')
p = soup.find('p', class_='para1')
p['class'] = 'new_class'
print(p)

正则表达式

正则表达式知识点

正则表达式是一种用于匹配字符串的模式。它通过字符组成规则定义了搜索文本中特定模式的方法。Python中的re模块提供了使用正则表达式的功能。

  • . 表示任意字符。
  • \d表示数字,\D表示非数字。
  • \w表示单词字符,即az、AZ、0~9和下划线。
  • \W表示非单词字符。
  • \s表示空白符,包括空格、制表符、换行符等。
  • \S表示非空白符。
  • ^表示匹配行首。
  • $表示匹配行尾。
  • *表示匹配前面的字符零次或多次。
  • +表示匹配前面的字符一次或多次。
  • ?表示匹配前面的字符零次或一次。
  • {m}表示匹配前面的字符m次。
  • {m,n}表示匹配前面的字符m到n次。
  • […]表示匹配方括号中任意一个字符。
  • [^…]表示匹配除了方括号中给出的字符以外的任意一个字符。
  • (…)表示匹配括号中的表达式。
  • re.match():从字符串的开头开始匹配,只匹配一次。
  • re.search():在字符串中匹配第一个符合条件的内容。
  • re.findall():在字符串中匹配所有符合条件的内容并以列表的形式返回。
  • re.sub():用一个新的字符串替换掉匹配到的所有内容。
  • re.compile():将正则表达式转化为一个正则表达式对象,以便于复用。

正则表达式案例

(1) 匹配手机号码

import rephone_nums = ['13912345678', '13812345678', '13512345678', '13612345678', '13712345678']pattern = r'^1[3-9]\d{9}$'
for phone_num in phone_nums:if re.match(pattern, phone_num):print(f'{phone_num}是一个合法的手机号码')else:print(f'{phone_num}不是一个合法的手机号码')

(2) 替换HTML文档中的标签

import rehtml_doc = """
<html>
<head><title>这是标题</title></head>
<body>
<p class="para1">第一段落</p>
<p class="para2">第二段落</p>
</body>
</html>
"""pattern = r'<.*?>'
new_doc = re.sub(pattern, '', html_doc)
print(new_doc)

(3) 提取金融数据

import retext = '2019年GDP增速为7.5%,同比增长0.3个百分点;CPI同比上涨2.5%,环比上涨0.3%。'pattern1 = r'\d+.\d+%'
pattern2 = r'[A-Z]+'
num_list = re.findall(pattern1, text)
unit_list = re.findall(pattern2, text)
for i in range(len(num_list)):print(f'{num_list[i]} {unit_list[i]}')

正则表达式实战

import os
import re
from collections import Counterdef get_word_counts(folder_path):"""统计指定文件夹中所有文本文件中各个单词的出现频率,并返回一个Counter对象。"""word_counter = Counter()for root, dirs, files in os.walk(folder_path):for file in files:if file.endswith('.txt'):file_path = os.path.join(root, file)# 读取文本文件内容with open(file_path, 'r', encoding='utf-8') as f:text = f.read()# 使用正则表达式去除标点符号、换行符等非单词字符pattern = r'\b\w+\b'words = re.findall(pattern, text)# 对单词列表进行计数,并将结果更新到Counter对象中word_counter.update(words)return word_counterif __name__ == '__main__':folder_path = 'test'word_counter = get_word_counts(folder_path)# 输出前十个出现频率最高的单词及其出现次数top_n = 10print(f'Top {top_n} words:')for word, count in word_counter.most_common(top_n):print(f'{word:<10} {count}')

字体反爬

  1. 解析woff文件

很多网站会使用woff格式的字体文件来渲染文本内容,爬虫需要先下载这些字体文件,并解析出字符与字形之间的对应关系,然后才能正常解密文本内容。

  1. 使用fontTools库

Python中有一个非常优秀的字体解析库叫做fontTools,可以帮助我们轻松地解析字体文件,并生成字形对应表。使用该库可以避免自行解析字体文件所遇到的各种问题。

  1. 使用在线字体解密工具

有些网站提供了在线字体解密工具,如FontSpider、字体反爬插件等,可以帮助我们快速地解密字体。不过,使用这种方法需要注意隐私安全问题。

(1) 解析woff文件

import base64
from fontTools.ttLib import TTFont# 下载字体文件并保存为base64编码字符串
font_url = 'http://example.com/font.woff'
font_base64 = '...'# 将base64编码字符串解码并保存到本地
with open('font.woff', 'wb') as f:font_data = base64.b64decode(font_base64)f.write(font_data)# 解析字体文件并获取字形对应表
font = TTFont('font.woff')
cmap = font.getBestCmap()# 定义替换规则
replace_dict = {'&#x8FDE;': '0','&#xE4CD;': '1','&#xFAF5;': '2','&#xEA72;': '3','&#xEDB4;': '4','&#xF640;': '5','&#xF62F;': '6','&#xEB10;': '7','&#xF9D4;': '8','&#xF15C;': '9',
}# 替换文本内容
text = '&#xE4CD;&#xF15C;&#xF15C;'
for key, value in replace_dict.items():text = text.replace(key, value)# 输出结果
print(text)

(2) 使用fontTools库

import requests
from io import BytesIO
from fontTools.ttLib import TTFont# 下载字体文件并用fontTools库读取
font_url = 'http://example.com/font.woff'
font_data = requests.get(font_url).content
font = TTFont(BytesIO(font_data))# 获取字形对应表
cmap = font.getBestCmap()# 定义替换规则
replace_dict = {'&#x8FDE;': '0','&#xE4CD;': '1','&#xFAF5;': '2','&#xEA72;': '3','&#xEDB4;': '4','&#xF640;': '5','&#xF62F;': '6','&#xEB10;': '7','&#xF9D4;': '8','&#xF15C;': '9',
}# 解密文本内容
text = '&#xE4CD;&#xF15C;&#xF15C;'
for key, value in replace_dict.items():glyph_id = cmap[int(key[3:-1], 16)]text = text.replace(key, value)# 输出结果
print(text)

(3) 使用在线字体解密工具

import requests
from fontSpider import FontSpider# 下载字体文件并保存为base64编码字符串
font_url = 'http://example.com/font.woff'
r = requests.get(font_url)
font_base64 = FontSpider(r.content).to_base64()# 使用在线字体解密工具解密文本内容
text = '&#xE4CD;&#xF15C;&#xF15C;'
response = requests.post('http://font-spider.com/api/v1/decrypt', data={'font': font_base64, 'text': text})
result = response.json()# 输出结果
print(result['data'])

需要注意的是,使用在线字体解密工具可能存在隐私安全问题,因此尽量避免在生产环境中使用。

Scrapy入门

  1. 工程结构

Scrapy的工程具有标准的项目结构,通常包含以下几个文件:

  • scrapy.cfg:Scrapy项目配置文件。
  • items.py:定义爬取的数据结构。
  • middlewares.py:管理请求和响应,例如User-Agent、代理等。
  • pipelines.py:配置到底怎么样后续处理item。
  • settings.py:保存爬虫的参数设置。
  • spiders/:保存爬虫代码的目录。
  1. 爬虫流程

Scrapy的爬虫流程如下:

  • 发起请求:通过定义好的URL地址来发送HTTP请求。
  • 下载页面:Scrapy会自动下载对应的页面,或使用第三方库,如requests、Selenium等。
  • 解析页面:使用XPath或CSS选择器解析网页内容。
  • 保存数据:将解析得到的数据保存到本地或数据库中。
  1. Scrapy组件

Scrapy具有以下几个重要组件:

  • Spider:定义如何抓取某个站点,包括如何跟进链接、如何分析页面内容等。
  • Item:定义爬取的数据结构。
  • Pipeline:负责处理Item,如清理、过滤、存储到数据库等。
  • Downloader:负责下载网页,并将结果传递给Spider。
  • Scheduler:负责调度Spider发起请求,并将结果传递给Downloader。

Scrapy实例

  1. 爬取豆瓣电影TOP250的数据
import scrapyclass DoubanMovieSpider(scrapy.Spider):name = 'douban_movie'allowed_domains = ['movie.douban.com']start_urls = ['https://movie.douban.com/top250']def parse(self, response):for info in response.xpath('//div[@class="info"]'):yield {'title': info.xpath('div[@class="hd"]/a/span/text()').extract_first(),'score': info.xpath('div[@class="bd"]/div[@class="star"]/span[@class="rating_num"]/text()').extract_first(),'director': info.xpath('div[@class="bd"]/p/text()')[0].strip() if len(info.xpath('div[@class="bd"]/p')) == 2 else '','year': info.xpath('div[@class="bd"]/p/text()')[-1].strip().replace('(', '').replace(')', '') if len(info.xpath('div[@class="bd"]/p')) == 2 else info.xpath('div[@class="bd"]/p/text()')[-1].strip()}next_page = response.xpath('//span[@class="next"]/a/@href')if next_page:yield scrapy.Request(url=response.urljoin(next_page.extract_first()), callback=self.parse)
  1. 使用middlewares设置User-Agent
import random
from scrapy.downloadermiddlewares.useragent import UserAgentMiddlewareclass RandomUserAgentMiddleware(UserAgentMiddleware):user_agents = ['Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299','Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36','Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36','Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36','Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36','Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; AS; rv:11.0) like Gecko','Mozilla/5.0 (Windows NT 6.3; WOW64; rv:57.0) Gecko/20100101 Firefox/57.0','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 Edge/16.16299']def process_request(self, request, spider):user_agent = random.choice(self.user_agents)request.headers.setdefault('User-Agent', user_agent)
  1. 将数据写入MySQL数据库
import pymysql
from scrapy.exceptions import DropItemclass MysqlPipeline(object):def __init__(self, mysql_host, mysql_db, mysql_user, mysql_pwd, mysql_table):self.mysql_host = mysql_hostself.mysql_db = mysql_dbself.mysql_user = mysql_userself.mysql_pwd = mysql_pwdself.mysql_table = mysql_tabledef process_item(self, item, spider):if item['title'] and item['score']:db = pymysql.connect(host=self.mysql_host, user=self.mysql_user, password=self.mysql_pwd, db=self.mysql_db)cursor = db.cursor()sql = "INSERT INTO %s (title, score, director, year) VALUES ('%s', '%s', '%s', '%s')" % (self.mysql_table, item['title'], item['score'], item['director'], item['year'])try:cursor.execute(sql)db.commit()except Exception as e:db.rollback()raise DropItem("Failed to insert item: {}".format(e))finally:db.close()return item

完结

本文链接:https://my.lmcjl.com/post/11612.html

展开阅读全文

4 评论

留下您的评论.