[TOC] #### 1. 第一个爬虫程序开发 --- ```python from urllib.request import urlopen url = "https://www.baidu.com" resp = urlopen(url) # 读取页面内容 然后解码 # print(resp.read().decode("utf-8")) # 此时拿到的是页面源代码 with open("mybaidu.html", mode="w", encoding="utf-8") as f: f.write(resp.read().decode("utf-8")) ``` #### 2. requests 模块使用 --- 前面我们使用 `urllib` 来抓取页面源代码,这个是 python 内置的一个模块。但是,它并不是我们常用的爬虫工具 常用的抓取页面的模块通常使用一个第三方模块 `requests` + 这个模块的优势就是比 `urllib` 还要简单,并且处理各种请求都比较方便 既然是第三方模块,那就需要先进行安装,安装方法: ```bash pip install requests ``` 如果安装速度慢的话,可以改用国内的源进行下载安装: ```bash pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple requests ``` 使用示例 ```python import requests # 爬取百度的源代码 url = "https://www.baidu.com" resp = requests.get(url) resp.encoding = "utf-8" print(resp.text) # 拿到页面源代码 ``` 处理 get 请求 ```python import requests content = input('请输入你要检索的内容:') url = f"https://www.sogou.com/web?query={content}" header = { # 添加一个请求头信息,UA 的默认值示例:python-requests/2.34.2 "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36" } # 处理一个小小的反爬 resp = requests.get(url, headers=header) # print(resp.text) # 查看请求头信息 print(resp.request.headers) ``` 处理 post 请求 ```python import requests url = "https://fanyi.baidu.com/sug" data = { "kw": input("请输入一个单词:") } resp = requests.post(url, data=data) # print(resp.text) # 拿到的是文本字符串 print(resp.json()) # 此时拿到的直接是 json 数据 ``` 处理很多参数的 get 请求 ```python import requests url = "https://movie.douban.com/j/chart/top_list" # 地址中的参数 params = { "type": "24", "interval_id": "100:90", "action": "", "start": "0", "limit": "20" } # 处理反爬 headers = { "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36" } resp = requests.get(url, params=params, headers=headers) # print(resp.text) print(resp.json()) ``` #### 3. 数据解析概述 --- 我们基本上掌握了抓取整个网页的基本技能。但是呢,大多数情况下,我们并不需要整个网页的内容,只是需要一小部分 这就涉及到了数据提取的问题,本文提供四种解析方式: + re 解析(正则表达式) + bs4 解析 + xpath 解析 + pyquery 解析(类似于 jQuery 选择器) 这四种方式可以混合进行使用,完全以结果做导向,只要能拿到你想要的数据,用什么方案并不重要,后续再考虑性能问题 #### 4. 正则表达式 --- 正则表达式(Regular Expression):一种使用表达式的方式对字符串进行匹配的语法规则 我们抓取到的网页源代码本质上就是一个超长的字符串,想从里面提取内容,用正则表达式再合适不过了 正则表达式的优缺点: + 优点:速度快、效率高、准确性高 + 缺点:新手上手难度有点高 不过只要掌握了正则编写的逻辑关系,写出一个提取页面内容的正则其实并不复杂 正则的语法:使用元字符进行排列组合,用来匹配字符串。在线测试正则表达式:<https://tool.oschina.net/regex> 元字符:具有固定含义的特殊字符 常用元字符: ```plaintext . 匹配除换行符以外的任意字符 \w 匹配字母、数字、下划线 \s 匹配任意的空白符 \d 匹配数字 \n 匹配一个换行符 \t 匹配一个制表符 ^ 匹配字符串的开始 $ 匹配字符串的结尾 \W 匹配非字母、数字、下划线 \D 匹配非数字 \S 匹配非空白符 a|b 匹配字符a或字符b () 匹配括号内的表达式,也表示一个组 [...] 匹配字符组中的字符 [^...] 匹配除了字符组中字符的所有字符 ``` 常用用法: ```plaintext [a-zA-Z0-9] ``` 量词:控制前面的元字符出现的次数 ```plaintext * 零次或更多次 + 一次或更多次 ? 零次或一次 {n} n次 {n,} n次或更多次 {n,m} n到m次 ``` 贪婪匹配和惰性匹配(写爬虫用的最多的就是这个惰性匹配) ```plaintext .* 贪婪匹配 .*? 惰性匹配 ``` 案例: ```plaintext str: 玩儿吃鸡游戏,晚上一起上游戏,干嘛呢?打游戏啊 reg: 玩儿.*游戏 此时匹配的是: 玩儿吃鸡游戏,晚上一起上游戏,干嘛呢?打游戏 reg: 玩儿.*?游戏 此时匹配的是: 玩儿吃鸡游戏 ``` 惰性匹配标签 ```plaintext str: <div class="abc"><div>胡辣汤</div><div>油条</div></div> reg: <div>.*?</div> 结果(2处匹配): <div>胡辣汤</div> <div>油条</div> ``` 所以我们能发现这样一个规律:`.*?` 表示尽可能少的匹配 `.*` 表示尽可能多的匹配,记住这个规律,后面写爬虫会用到 #### 5. re 模块数据解析 --- `re` 模块是 Python 的内置模块,不需要另外安装,直接导入使用即可 ```python import re result1 = re.findall("n", "我是king") print(result1) # 使用 r 就不用担心正则表达式中的转义问题了 result2 = re.findall(r"\d+", "我今年18岁,我有200块") print(result2) # 这个是重点,多多联系 # re.finditer 返回的是迭代器对象 result3 = re.finditer(r"\d+", "我今年18岁,我有200块") for item in result3: # 从迭代器中拿到内容 print(item.group()) # 从匹配到的结果中拿到数据 # search 只会匹配到第一次匹配的内容 result4 = re.search(r"\d+", "我叫 king,今年20岁,我的班级是5年级4班") print(result4.group()) # match 是从字符串的开头进行匹配的,类似在正则前面加上了 ^ result5 = re.match(r"\d+", "我叫 king,今年20岁,我的班级是5年级4班") print(result5) # 预加载,提前把正则对象加载完毕 obj = re.compile(r"\d+") # 直接把加载好的正则进行使用 result6 = obj.findall("我今年18岁,我有200块") print(result6) ``` `re` 提取分组数据 ```python import re s = """ <div class='西游记'><span id='10010'>中国联通</span></div> <div class='西游记'><span id='10086'>中国移动</span></div> """ # 想要提取数据必须用小括号括起来,可以单独起名字 # (?P<名字>正则) # 提取数据的时候,需要 group("名字") obj = re.compile(r"<span id='(?P<id>\d+)'>(?P<name>.*?)</span>") result = obj.finditer(s) for item in result: id = item.group("id") name = item.group("name") print(id, name) ``` #### 6. 抓取豆瓣 TOP250 数据 --- ```python import requests import re # 思路 # 1.拿到页面源代码 # 2.编写正则,提取页面数据 # 3.保存数据 f = open('top250.csv', mode="w", encoding="utf-8") headers = { 'user-agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36" } resp = requests.get("https://movie.douban.com/top250", headers=headers) # resp.encoding = "utf-8" # 解决乱码问题 pageSource = resp.text # 页面源代码 # 编写正则表达式 # re.S 可以让正则中的 . 匹配换行符 obj = re.compile(r'<div class="item">.*?<span class="title">(?P<name>.*?)</span>' r'.*?<p>.*?导演: (?P<director>.*?) .*?<br>(?P<year>.*?) .*?' r'<span class="rating_num" property="v:average">(?P<score>.*?)</span>' r'.*?<span>(?P<eval>.*?)人评价</span>', re.S) result = obj.finditer(pageSource) for item in result: name = item.group("name") director = item.group("director") year = item.group("year").strip() score = item.group("score").strip() eval = item.group("eval").strip() # 如果觉得 low,可以更换成 csv 模块,进行数据写入 f.write(f"{name},{director},{year},{score},{eval}\n") # print(name,director,year,score,eval) f.close() resp.close() print("豆瓣TOP250提取完毕。") # 如何翻页提取 ? # (页数 - 1) * 25 => start # 解决方案:写个循环提取即可 ``` #### 7. 抓取电影天堂电影信息 --- 电影天堂网址:<https://www.dytt8899.com> 目标:拿到 “2026必看热片” 中的所有电影片名和下载地址(信息在详情页) ```python import requests import re url = "https://www.dytt8899.com" resp = requests.get(url) resp.encoding = "gbk" # print(resp.text) # 1.提取必看热片部分的 HTML 代码 obj1 = re.compile(r'2026必看热片.*?<ul>(?P<html>.*?)</ul>', re.S) result1 = obj1.search(resp.text) html = result1.group('html') # 2.提取 a 标签中的 href 值 obj2 = re.compile(r"<li><a href='(?P<href>.*?)' title") result2 = obj2.finditer(html) obj3 = re.compile(r'◎片 名 (?P<name>.*?)<br />.*?<div class=player_list>.*?<li><a href="(?P<url>.*?)">', re.S) for item in result2: child_url = url + item.group('href') child_resp = requests.get(child_url) child_resp.encoding = 'gbk' result3 = obj3.search(child_resp.text) print(result3.group('name'), result3.group('url')) # break # 调试时使用,中断循环 ``` #### 8. bs 数据解析用法 --- 安装 bs4 ```bash pip install bs4 ``` 基本使用 ```python from bs4 import BeautifulSoup html = """ <ul> <li><a href="zhangwuji.com">张无忌</a></li> <li id="abc"><a href="zhouxingchi.com">周星驰</a></li> <li><a href="zhubajie.com">猪八戒</a></li> <li><a href="wuzetian.com">武则天</a></li> </ul> """ # 初始化 BeautifulSoup 对象 page = BeautifulSoup(html, "html.parser") # 查找某个元素,只会找到一个结果 # page.find("标签名", attrs={"属性": "值"}) # 找到一堆结果 # page.find_all("标签名", attrs={"属性": "值"}) # page.find() 使用示例 li = page.find("li", attrs={"id": "abc"}) print(li) a = li.find('a') # 可以继续往内层元素找 print(a) print(a.text) # 标签内容 print(a.get('href')) # 获取属性值 # page.find_all() 使用示例 li_list = page.find_all('li') # 返回多个 li 标签 for li in li_list: print(li.text) ``` 实战案例 ```python import requests from bs4 import BeautifulSoup url = "https://www.umtuku.com" headers = { "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36" } resp = requests.get(url, headers=headers) resp.encoding = "utf-8" main_page = BeautifulSoup(resp.text, "html.parser") li_list = main_page.find_all('li', attrs={'class': 'i_list list_n2'}) n = 1 # 图片名称 for li in li_list: href = li.find('a').get('href') resp2 = requests.get(href, headers=headers) resp2.encoding = "utf-8" page = BeautifulSoup(resp2.text, "html.parser") image = page.find('div', attrs={'id': 'image_div'}).find('img').get('src') # print(image) # 下载图片 img_resp = requests.get(image, headers=headers) # print(img_resp.text) # 注意,图片不是文本,不能获取 text 的内容 # print(img_resp.content) # 这里打印的是字节 with open(f"{n}.jpg", mode="wb") as f: # 注意,此时写入到文件的是字节,所以必须是 wb f.write(img_resp.content) # 把图片信息写入到文件中 print(f"{n}图片下载完毕") n += 1 ``` #### 9. xpath 数据解析用法 --- XPath 是一门在 XML 文档中查找信息的语言,XPath 可用来在 XML 文档中对元素和属性进行遍历。而我们熟知的 HTML 恰巧属于 XML 的一个子集,所以完全可以用 xpath 去查找 html 中的内容 首先,先了解几个概念 ```xml <book> <id>123</id> <name>野花遍地香</name> <price>1.23</price> <author> <nick>张无忌</nick> <nick>周芷若</nick> </author> </book> ``` 在上述内容中 + book,id,name,price 都被称为节点 + id,name,price,author 被称为 book 的子节点 + book 被称为 id,name,price,author 的父节点 + id,name,price,author 被称为同胞节点 有了这些基础知识后,就可以开始了解 xpath 的基本语法了。想要使用 `xpath`,需要先安装 `lxml` 模块 ```bash pip install lxml ``` xpath 处理 xml ```python from lxml import etree # 如果 pycharm 报错,可以考虑这种导入方式 # from lxml import html # etree = html.etree xml = """ <book> <id>1</id> <name>野花遍地香</name> <price>1.23</price> <nick>臭豆腐</nick> <author> <nick id="10086">张无忌</nick> <nick id="10010">周芷若</nick> <nick class="jay">周杰伦</nick> <nick class="jolin">蔡依林</nick> <div> <nick>神秘人</nick> </div> </author> <partner> <nick id="ppc">胖胖陈</nick> <nick id="ppbc">胖胖不陈</nick> </partner> </book> """ # print(xml) # 此时练习只能用 XML et = etree.XML(xml) # 基本使用 result1 = et.xpath("/book") # / 表示根节点 result2 = et.xpath("/book/name") # 在xpath中间的/表示的是儿子 result3 = et.xpath("/book/name/text()") # text() 拿文本,返回的是一个列表 result4 = et.xpath('/book//nick/text()') # // 表示子孙后代 result5 = et.xpath('/book/*/nick/text()') # /*/ 表示有父节点的子孙后代 result6 = et.xpath('/book/author/nick[@class="jay"]/text()') # [@属性名="值"] 表示属性筛选 result7 = et.xpath('/book/partner/nick/@id') # 最后一个/表示拿到nick里面的id的内容,@属性 可以直接拿到属性值 print(result1) print(result2) print(result3[0]) print(result4) print(result5) print(result6) print(result7) ``` xpath 处理 html ```python from lxml import etree html = """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <ul> <li><a href="https://www.baidu.com">百度</a></li> <li><a href="https://www.google.com">谷歌</a></li> <li><a href="https://www.sogou.com">搜狗</a></li> </ul> <ol> <li><a href="feiji">飞机</a></li> <li><a href="dapao">大炮</a></li> <li><a href="huoche">火车</a></li> </ol> <div class="job">李嘉诚</div> <div class="common">胡辣汤</div> </body> </html> """ et = etree.HTML(html) result1 = et.xpath('/html/body/ul/li[2]/a/text()') # [2] 表示第二个 li result2 = et.xpath("//li") print(result1) for item in result2: href = item.xpath('./a/@href')[0] # ./ 表示当前节点 text = item.xpath('./a/text()')[0] # ./ 表示当前节点 print(href, text) ``` xpath 实战案例:抓取技术博客文章 ```python import requests from lxml import etree domain = "https://www.itqaq.com" url = domain + "/index/cate/5.html" resp = requests.get(url) resp.encoding = "utf-8" et = etree.HTML(resp.text) result = et.xpath('//div[@class="col-xs-6 col-md-3 thumb-box"]') for item in result: href= item.xpath('./a/@href')[0] title = item.xpath('./a/img/@alt')[0] print(title + ": " + domain + href) ``` #### 10. pyquery 数据解析用法 --- 需要先安装 ```bash pip install pyquery ``` 基础用法 ```python from pyquery import PyQuery # html = '<li><a href="https://www.baidu.com">百度</a></li>' html = """ <ul> <li class="baidu"><a href="https://www.baidu.com">百度</a></li> <li class="google"><a href="https://www.google.com">谷歌</a></li> <li class="sogou" id="dog"><a href="https://www.sogou.com">搜狗</a></li> </ul> """ # 加载 html 内容 p = PyQuery(html) # print(p) # print(type(p)) # pyquery 对象 # pyquery对象直接(css选择器) # a = p('a') # 通过标签选择器获取 # print(a) # print(type(a)) # 依然是 pyquery 对象 # 所以可以使用链式操作 # a2 = p('li')('a') # print(a2) # 更推荐后代选择器的写法 # a3 = p('li a') # print(a3) # a4 = p('.google') # class="google" # a5 = p('.google a') # href = p('#dog a').attr('href') # 获取属性值 # text = p('#dog a').text() # 获取标签文本 # print(a4) # print(a5) # print(href) # print(text) # 坑:如果多个标签同时拿属性,默认只能拿到第一个 href2 = p('li a').attr('href') print(href2) # 多个标签拿属性:获取所有 a 标签的 href it = p('li a').items() print(it) # generator object for item in it: print(item.attr('href')) # 快速总结: # 1.pyquery(选择器) # 2.items() 当选择器选择的内容很多的时候,需要一个一个处理 # 3.attr() 获取属性信息 # 4.text() 获取标签文本 div = '<div><span>我爱你</span></div>' p2 = PyQuery(div) html = p2('div').html() text = p2('div').text() print(html) # 全都要(<span>我爱你</span>) print(text) # 只要文本,所有的 HTML 标签被过滤掉(我爱你) ```