赞
踩
爬虫的四个主要步骤:
正则表达式(regular expression),又称为规则表达式,描述了一种字符串匹配的模式(pattern),通常被用来检索、替换那些符合某个模式(规则)的文本。
正则表达式是由普通字符(例如字符a到z)+ 特殊字符(称为“元字符”)组成的文字模式。
正则表达式被广泛的集成到各种文本编辑器/文本处理工具当中
语法 | 说明 | 表达式实例 | 完整匹配的字符串 |
---|---|---|---|
一般字符 | 匹配自身 | abc | abc |
. | 匹配任意除了换行符"\n"外的字符 | a.c | abc |
\ | 转义字符,使后一个字符改变原来的意思 | a.c | a.c |
[…] | 匹配[ ]内任一字符。对应的位置可以是字符集中任意字符。字符集中的字符可以逐个列出,也可以给出范围,如[abc]或[a-c]。如果第一个字符是^则表示取反,如[^abc]表示不是abc的其他字符。 | a[bcd]e | abe、ace、ade |
- | 在[ ]内表示字符范围 | wo[a-c]ld | woald、wocld |
字符 | 含义 | 举例 | 匹配的字符串 |
---|---|---|---|
* | 匹配前一个字符0次或者无限次 | abc* | ab、abccccc |
? | 匹配前一个字符0次或1次 | abc? | ab、abc |
+ | 匹配前一个字符1次或者无限次 | abc+ | abc、abcccc |
{m} | 匹配前一个字符m次 | ab{2}c | abbc |
{m,n} | 匹配前一个字符m至n次。m和n可以省略:若省略m,则匹配0至n次;若省略n,则至少匹配m次。 | ab{1,2}c | abc、abbc |
正则表达式实现步骤:
# 1.使用compile()函数将正则表达式的字符串形式编译为一个Pattern对象 # 注意:re对特殊字符进行转义, 如果使用原始字符串,只需加一个r前缀 import re text = "2020-12-12 2019-5-3 2020/10/10" pattern = re.compile(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})') # 命名分组 # 2.通过Pattern对象对文本进行匹配查找,获得匹配结果,一个Match对象 # search从给定的字符串中寻找一个符合规则的字符串,返回回来一个 result = re.search(pattern, text) print(result) # 3.使用Match对象提供的属性和方法获取信息,根据需要进行操作 print(result.group()) # 返回的是匹配到的文本信息 2020-12-12 print(result.groups()) # 返回的是位置分组信息 ('2020', '12', '12') print(result.groupdict()) #返回的是关键字分组信息 {'year': '2020', 'month': '12', 'day': '12'}
正则表达式编译成 Pattern 对象, 可以利用 pattern 的一系列方法对文本进行匹配查找了。
Pattern 对象的一些常用方法主要有:
match 方法用于查找字符串的头部(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回, 而不是查找所有匹配的结果。它的一般使用形式如下:
常量即表示不可更改的变量,一般用于做标记。下图是常用的四个模块常量:
search 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有 匹配的结果,它的一般使用形式如下:
当匹配成功时,返回一个 Match 对象,如果没有匹配上,则返回 None。
findall 方法搜索整个字符串,获得所有匹配的结果。使用形式如下:
finditer 方法的行为跟 findall 的行为类似,也是搜索整个字符串,获得所有匹配的结果。但它返回一个顺序访问每 一个匹配结果(Match 对象)的迭代器。
split 方法按照能够匹配的子串将字符串分割后返回列表,它的使用形式如下:
sub 方法用于替换。它的使用形式如下:
正则split和sub方法:
import re text = "1+2-3*4-5/5" # ************split************** pattern = re.compile(r'\+|-|\*|/') result = re.split(pattern, text) print(result) # *************sub********** def repl_string(matchObj): items = matchObj.groups() print(items) return '-'.join(items) text = '2010/10/10 2020/12/12 2010-5-3' pattern = re.compile(r'(\d{4})/(\d{1,2})/(\d{1,2})') result = re.sub(pattern,repl_string, text) print(result) ['1', '2', '3', '4', '5', '5'] ('2010', '10', '10') ('2020', '12', '12') 2010-10-10 2020-12-12 2010-5-3
基于requests和正则的猫眼电影TOP100定向爬虫:
项目介绍: 应用requests库和正则表达式来抓取猫眼电影TOP100的电影名称、时间、评分、图片等信息。
项目分析:
1. 需求分析,明确采集的网址。
https://maoyan.com/board
2. 爬, requests数据采集库
3. 取,正则表达式数据解析库
4. 存, json格式存储到文件
import codecs import json import re import time from urllib.error import HTTPError import requests from colorama import Fore from fake_useragent import UserAgent def down_page(url): try: ua = UserAgent() headers = { 'User-Agent':ua.random, 'Cookie':'__mta=49747353.1586608791319.1586618569026.1586618581654.11; uuid_n_v=v1; uuid=73354BA07BAE11EA81AF6B12644ABFB70CD0E584BFFF4456AE4AA96286336568; _csrf=1d3978fad0d68febffd13896596effb6fa9633349bd601af3678a1bcd78c47b9; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1586608791,1586609306; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1586618649; _lxsdk_cuid=1716940dec2c8-018cbd125127c58-3e6e4647-100200-1716940dec2c8; _lxsdk=73354BA07BAE11EA81AF6B12644ABFB70CD0E584BFFF4456AE4AA96286336568; mojo-uuid=1a2bc23bad16f412c35c00f9b881946a; __mta=49747353.1586608791319.1586618086296.1586618566417.10; _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; _lxsdk_s=17169b3dce9-5ef-6b5-ccd%7C%7C28; mojo-trace-id=20; mojo-session-id={"id":"05adcf6f220fcf28a22a52d7640319db","time":1586616327477}', 'Host':'maoyan.com', } response = requests.get(url, headers=headers) except HTTPError as e: print(Fore.RED + '[-] 爬取网页%s失败:%s' %(url, e.reason)) return None else: return response.text def parse_html(html): """通过正则表达式对html解析获取电影名称、时间、排行、图片等信息""" pattern = re.compile( '<dd>' + '.*?<i class="board-index.*?">(\d+)</i>' # <i class="board-index board-index-1">1</i> + '.*?<img data-src="(.*?)" alt="(.*?)"'# <img data-src="xxxxxx" alt="我和我的祖国" > + '.*?<p class="star">(.*?)</p>' #<p class="star">主演:黄渤,张译,韩昊霖</p> + '.*?<p class="releasetime">(.*?)</p>' #<p class="releasetime">上映时间:2019-09-30</p> + '.*?</dd>', re.S ) # finditer返回的是迭代器, findall返回的是列表 items = re.finditer(pattern, html) for item in items: yield { 'index':item.groups()[0], 'image':item.groups()[1], 'title':item.groups()[2], 'star':item.groups()[3].strip().lstrip('主演:'), 'releasetime':item.groups()[4].lstrip('上映时间:') } def save_to_json(data, filename): """将爬取的数据写入json文件""" # with open(filename, 'ab') as f: # f.write(json.dumps(data, ensure_ascii=False, indent=4).encode('utf-8')) # print(Fore.GREEN + '[+] 保存电影 %s 的信息成功' %(data['title'])) with codecs.open(filename, 'a', 'utf-8') as f: f.write(json.dumps(data, ensure_ascii=False, indent=4)) print(Fore.GREEN + '[+] 保存电影 %s 的信息成功' % (data['title'])) def get_one_page(page=1): # url = 'https://maoyan.com/board/' url = 'https://maoyan.com/board/4?offset=%s' %((page-1)*10) html = down_page(url) # print(html) items = parse_html(html) # print(items) # item是字典 for item in items: save_to_json(item, 'maoyanTop100.json') def use_multi_thread(): # 使用多线程实现 from threading import Thread for page in range(1, 11): thread = Thread(target=get_one_page, args=(page,)) thread.start() print(Fore.GREEN + '[+]采集第%s页数据成功' % (page)) def use_thread_pool(): # 使用线程池 from concurrent.futures import ThreadPoolExecutor # 实例化线程池并指定线程池的线程个数 pool = ThreadPoolExecutor(100) pool.map(get_one_page, range(1, 11)) print('采集结束') if __name__ == '__main__': # for page in range(1,11): # get_one_page(page) # print(Fore.GREEN + '[+]采集第%s页数据成功' %(page)) # # 反爬虫策略:防止爬虫速度过快被限速,在采集数据的过程中,休眠一段时间 # time.sleep(0.5) # 使用多线程 # use_multi_thread() # 使用线程池 use_thread_pool()
lxml是python的一个解析库,支持HTML和XML的解析,支持XPath解析方式,而且解析效率非常高。
XPath (XML Path Language) 是一门在 xml文档中查找信息的语言,可用来在 xml /html文档中对元素和属性进行遍历。
查询更多XPath的用法: https://www.w3school.com.cn/xpath/xpath_syntax.asp
from lxml import etree
html=etree.HTML(text)
result=etree.tostring(html)
html=etree.parse('xxx.html')
result=etree.tostring(html,pretty_print=True)
谓语用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中。
基于requests和XPath的猫眼电影TOP100定向爬虫:
项目介绍: 应用requests库和Xpath来抓取TIOBE编程语言的去年名次、今年名词、编程语言名词、评级和变化率等信息。
项目分析:
1. 需求分析,明确采集的网址。
https://www.tiobe.com/tiobe-index/
2. 爬, requests数据采集库
3. 取,Xpath数据解析库
4. 存, csv格式存储到文件
import codecs import json import re import time from urllib.error import HTTPError import requests from colorama import Fore from fake_useragent import UserAgent from lxml import etree def down_page(url): try: ua = UserAgent() headers = { 'User-Agent':ua.random, 'Cookie':'__mta=49747353.1586608791319.1586618569026.1586618581654.11; uuid_n_v=v1; uuid=73354BA07BAE11EA81AF6B12644ABFB70CD0E584BFFF4456AE4AA96286336568; _csrf=1d3978fad0d68febffd13896596effb6fa9633349bd601af3678a1bcd78c47b9; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1586608791,1586609306; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1586618649; _lxsdk_cuid=1716940dec2c8-018cbd125127c58-3e6e4647-100200-1716940dec2c8; _lxsdk=73354BA07BAE11EA81AF6B12644ABFB70CD0E584BFFF4456AE4AA96286336568; mojo-uuid=1a2bc23bad16f412c35c00f9b881946a; __mta=49747353.1586608791319.1586618086296.1586618566417.10; _lx_utm=utm_source%3DBaidu%26utm_medium%3Dorganic; _lxsdk_s=17169b3dce9-5ef-6b5-ccd%7C%7C28; mojo-trace-id=20; mojo-session-id={"id":"05adcf6f220fcf28a22a52d7640319db","time":1586616327477}', 'Host':'maoyan.com', } response = requests.get(url, headers=headers) except HTTPError as e: print(Fore.RED + '[-] 爬取网页%s失败:%s' %(url, e.reason)) return None else: return response.text def parse_html(html): """通过Xpath对html解析获取电影名称、时间、排行、图片等信息""" # 1.将传入的文档内容通过lxml解析器进行解析 html = etree.HTML(html) # 2. 通过Xpath语法获取电影的信息 movies = html.xpath('//dl[@class="board-wrapper"]/dd') for movie in movies: index = movie.xpath('./i/text()')[0], image = movie.xpath('.//img[@class="board-img"]/@data-src')[0] title = movie.xpath('.//img[@class="board-img"]/@alt')[0] star = movie.xpath('.//p[@class="star"]/text()')[0] releasetime = movie.xpath('.//p[@class="releasetime"]/text()')[0] yield { 'index': index, 'image': image, 'title': title, 'star': star.strip().lstrip('主演:'), 'releasetime': releasetime.lstrip('上映时间:') } def save_to_json(data, filename): """将爬取的数据写入json文件""" # with open(filename, 'ab') as f: # f.write(json.dumps(data, ensure_ascii=False, indent=4).encode('utf-8')) # print(Fore.GREEN + '[+] 保存电影 %s 的信息成功' %(data['title'])) with codecs.open(filename, 'a', 'utf-8') as f: f.write(json.dumps(data, ensure_ascii=False, indent=4)) print(Fore.GREEN + '[+] 保存电影 %s 的信息成功' % (data['title'])) def get_one_page(page=1): # url = 'https://maoyan.com/board/' url = 'https://maoyan.com/board/4?offset=%s' %((page-1)*10) html = down_page(url) # print(html) items = parse_html(html) # print(items) # item是字典 for item in items: save_to_json(item, 'maoyanTop100.json') if __name__ == '__main__': for page in range(1,11): get_one_page(page) print(Fore.GREEN + '[+]采集第%s页数据成功' %(page)) # 反爬虫策略:防止爬虫速度过快被限速,在采集数据的过程中,休眠一段时间 time.sleep(0.5)
基于requests和Xpath的TIOBE编程语言排行榜定向爬虫:
import csv import json import os import re import time from urllib.error import HTTPError import requests from colorama import Fore from fake_useragent import UserAgent from lxml import etree def down_page(url): try: ua = UserAgent() headers = { 'User-Agent':ua.random, } response = requests.get(url, headers=headers) except HTTPError as e: print(Fore.RED + '[-] 爬取网页%s失败:%s' %(url, e.reason)) return None else: return response.text def parse_html(html): """通过Xpath对html解析获取编程语言的去年名次,今年名次,编程语言名称,评级Rating和变化率Change等信息""" # 1.将传入的文档内容通过lxml解析器进行解析,返回Element信息 html = etree.HTML(html) # 2. 通过Xpath语法获取编程语言相关的信息 languages = html.xpath('//table[@id="top20"]/tbody/tr') for language in languages: now_rank = language.xpath('./td[1]/text()')[0] last_rank = language.xpath('./td[2]/text()')[0] name = language.xpath('./td[4]/text()')[0] rating = language.xpath('./td[5]/text()')[0] change = language.xpath('./td[6]/text()')[0] yield { 'now_rank': now_rank, 'last_rank': last_rank, 'name': name, 'rating': rating, 'change': change } def save_to_csv(data, filename): """将爬取的数据写入csv文件""" # 1). data是yield返回的字典对象 # 2). 以追加的方式打开文件并写入 # 3). 文件的编码格式是utf-8 # 4). 默认csv文件会写入空行, 解决:newkine:'' with open(filename, 'a', encoding='utf-8', newline='') as f: csv_write = csv.DictWriter(f, ['now_rank', 'last_rank', 'name', 'rating', 'change']) # 写入csv文件的表头 # csv_write.writeheader() csv_write.writerow(data) def get_one_page(page=1): url = 'https://www.tiobe.com/tiobe-index/' html = down_page(url) # print(html) filename = 'tiobe.csv' # print(os.path.dirname(filename)) items = parse_html(html) # print(items) # item是字典 for item in items: save_to_csv(item, filename) print(Fore.GREEN + '[+] 写入文件%s成功' %(filename)) if __name__ == '__main__': get_one_page()
Beautiful Soup就是Python的一个HTML或XML的解析库,可以用它来方便地从网页中提取数据。Beautiful Soup在解析时实际上依赖解析器, 可以选择的解析器如下表所示:
from bs4 import BeautifulSoup
# BeaufulSoup对象的初始化, 并指定html解析器是lxml
soup = BeautifulSoup(html, 'lxml')
# 把要解析的字符串以标准的缩进格式输出
soup.prettify()
# 1. 从bs4模块中导入BeautifulSoup
from bs4 import BeautifulSoup
# 2. 实例化BeautifulSoup对象,并通过指定的解析器解析html字符串的内容
html = """
<h1> BS4 </h1>
"""
soup = BeautifulSoup(html, 'lxml')
# 3. 将要解析的字符串以标准的缩进形式输出
print(soup.prettify())
直接调用节点的名称就可以选择节点元素,再调用string属性就可以得到节点内的文本了,这种选择方式速度非常快。如果单个节点结构层次非常清晰,可以选用这种方式来解析。
html = """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title class="mytitle">beautifulsoup</title> </head> <body> <table class="table" id="userinfo"> <tr> <td>姓名</td> <td>年龄</td> </tr> <tr> <td>张三</td> <td>20</td> </tr> <tr> <td>李四</td> <td>22</td> </tr> </table> <div> <img src="test.html" title="image title" alt="image alt"> </div> </body> </html> """ # 1. 从bs4模块中导入BeautifulSoup from bs4 import BeautifulSoup # 2. 实例化BeautifulSoup对象,并通过指定的解析器解析html字符串的内容 soup = BeautifulSoup(html, 'lxml') # 3. 节点选择器 # 3-1. 元素选择:只返回在html里面查询符合条件的第一个标签内容 # print(soup.title) # print(soup.img) # print(soup.tr) # 3-2. 嵌套选择器 # print(soup.body.table.tr.td) # print(soup.body.table.tr) # 3-3. 属性选择 # 3-3-1. 获取标签的名称:当爬虫过程中, 标签对象赋值给一个变量传递进函数时,想获取变量对应的标签,name属性就很有用 # print(soup.img.name) # 3-3-2. 获取标签的属性 # print(soup.table.attrs) # print(soup.table.attrs['class']) # print(soup.table['class']) # print(soup.table['id']) # 3-3-3. 获取文本内容 # print(soup.title.string) # print(soup.title.get_text()) # 3-4. 关联选择 # 3-4-1. 父节点和祖父节点 first_tr_tags = soup.table.tr # print(first_tr_tags.parent) # 父节点 # print(first_tr_tags.parents) # 祖父节点 # parents = first_tr_tags.parents # for parent in parents: # print('*********************') # print(parent) # 3-4-2. 子节点和子孙节点 # table_tags = soup.table # for children in table_tags.children: # print('*****') # print(children) # 3-4-3. 兄弟节点 # tr_tag = soup.table.tr # # print(tr_tag) # print(tr_tag.next_sibling.next_sibling)
节点选择器速度非常快,但面对复杂的选择不够灵活。Beautiful Soup提供的查询方法,比如find_all()和find()等实现灵活查询。
import re from bs4 import BeautifulSoup html = """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title class="mytitle">beautifulsoup</title> </head> <body> <table class="table" id="userinfo"> <tr class="item-1"> <td>姓名</td> <td>年龄</td> </tr> <tr class="item-2"> <td>张三</td> <td>20</td> </tr> <tr class="item-3"> <td>李四</td> <td>22</td> </tr> </table> <div> <img src="test.html" title="image title" alt="image alt"> </div> </body> </html> """ soup = BeautifulSoup(html, 'lxml') # print(soup.find_all(name='table')) # print(soup.find_all('table',attrs={'id':'userinfo'})) # print(soup.find_all('table', attrs={'class':'table'})) # print(soup.find_all('table', id='userinfo')) # print(soup.find_all('table', class_='table')) # print(soup.find_all('td', text=re.compile('\d{1,2}'), limit=1)) print(soup.find_all('tr', class_=re.compile('item-\d+'), limit=2))
import re from bs4 import BeautifulSoup html = """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <table class="table" id="userinfo"> <tr class="item-1"> <td>姓名</td> <td>年龄</td> </tr> <tr class="item-2"> <td>张三</td> <td>10</td> </tr> <tr class="item-3"> <td>李四</td> <td>20</td> </tr> </table> <div> <img src="download_images" title="image" alt="image alt"> </div> </body> </html> """ soup = BeautifulSoup(html,'lxml') # css选择器 print(soup.select('.item-3')) # print(soup.select('#userinfo')) print(soup.select('table tr td'))
import os from requests import HTTPError import requests from bs4 import BeautifulSoup from colorama import Fore from fake_useragent import UserAgent def download_page(url, parmas=None): """ 根据url地址下载html页面 :param url: :param parmas: :return: str """ try: ua = UserAgent() headers = { 'User-Agent': ua.random, } # 请求https协议的时候, 回遇到报错: SSLError # verify=Flase不验证证书 response = requests.get(url, params=parmas, headers=headers) except HTTPError as e: print(Fore.RED + '[-] 爬取网站%s失败: %s' % (url, str(e))) return None else: # content返回的是bytes类型, text返回字符串类型 return response.text def parse_html(html): # 实例化BeautifulSoup对象,并通过指定的解析器解析html字符串的内容 soup = BeautifulSoup(html, 'lxml') # 根据bs4的选择器获取章节的详情页链接和章节名称 book = soup.find('div', class_='book-mulu') # 获取该书籍对象 chapters = book.find_all('li') # 获取该数据所有的章节对应的li标签,返回的是列表 # 依次遍历每一个章节 for chapter in chapters: detail_url = chapter.a['href'] # print(detail_url) chapter_name = chapter.a.string # print(chapter_name) yield { 'detail_url':detail_url, 'chapter_name':chapter_name } def parse_detail_html(html): # 实例化BeautifulSoup对象,并通过指定的解析器解析html字符串的内容 soup = BeautifulSoup(html, 'lxml') # chapter_content = soup.find('div', class_='chapter_content') # 根据章节的详情页链接访问章节内容,string只拿出当前标签的文本信息,get_text()返回当前标签和子孙标签的所有文本信息 chapter_content = soup.find('div', class_='chapter_content').get_text() # print(chapter_content) return chapter_content.replace(' ', '') def get_one_page(): base_url = 'http://www.shicimingju.com' url = 'http://www.shicimingju.com/book/sanguoyanyi.html' dirname = '三国演义' if not os.path.exists(dirname): os.mkdir(dirname) print(Fore.GREEN + "创建书籍目录%s成功" %(dirname)) html = download_page(url) items = parse_html(html) for item in items: # 访问详情页链接 detail_url = base_url + item['detail_url'] # 生成文件存储路径 chapter_name = os.path.join(dirname, item["chapter_name"] + '.txt') chapter_html = download_page(detail_url) chapter_content = parse_detail_html(chapter_html) # 写入文件 with open(chapter_name, 'w', encoding='utf-8') as f: f.write(chapter_content) print(Fore.GREEN + '写入文件%s成功' %(chapter_name)) if __name__ == '__main__': get_one_page()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。