欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

python爬虫基础学习

程序员文章站 2022-03-21 07:52:48
...

爬虫 Webspider/Webcrawler 

针对互联网上的已有数据进行过滤、提取、下载、处理等操作

爬虫需要了解的知识

网页信息获取(以Chrome为例)

通过右键-检查,查看以下信息

    scheme:访问协议(http,https,ftp)

    host:域名

    port:端口号

    path:查询路径

    query-string:查询字符串(?后面的内容)

    anchor:锚点(往往是页码)

例如:  https://baike.baidu.com/item/Python/407303?fr=aladdin#4

请求方式:get(获取)、post(发送)

请求头:

    user-agent:设置为普通浏览器,以越过反爬虫

    referer:来源,设置为上一页url

    cookie:历史信息,登录记录

状态码:

    200:请求正常(但不代表数据正确)

    301:永久重定向(旧网址已迁移到新网址)

    302:临时重定向(返回登录页面)

    400:url不存在

    403:无权限访问(可能需要登录或者被识别)

    500:服务器内部出现问题

常用代理网址:

    西刺:http://www.xicidaili.com/

    快代理:http://kuaidaili.com/

    代理云:http://dailiyun.com/

查询当前ip地址:http://www.httpbin.org/ip

 

 

常用的库

urllib库

常用的语句

from urllib import request #导入url库的request
request.urlretrieve(url,'pathandtext') #获取网页内容,下载至本地
resp = request.urlopen(url) #获取url对应的响应
resp.read() #查看响应文本
resp.read().decode('utf-8') #对返回值编码以便阅读
handler = request.ProxyHandler({'http':'ip'}) #代理服务器设置(协议:代理ip)
opener = request.build_opener(handler) #创建代理人
resp4 = opener1.open('http://www.httpbin.org/ip')
#以代理人身份访问目标url('http://www.httpbin.org/ip'会返回访问者ip)

from urllib import parse #导入url库的parse
params = {'key1':'数据1',...} #设置查询参数
qs = parse.urlencode(params) #对非url格式(中文)进行编码
parse.parse_qs(qs) #对url格式进行解码
result = parse.urlparse(url) #拆解url为基本单元
result.scheme #查看访问协议
result.netloc #查看域名
result.path #查看路径
result.params #查看查询参数
result.query #查看查询字符串
result.fragment #查看锚点
headers = {'User-Agent':'...','Referer':'...'} #构造请求头
req1 = request.Request(url3,headers=headers,data=data_encode,method='POST')
#构造请求指令(包括网址、headers、data、method)

from http.cookiejar import CookieJar

#获取opener

def get_opener():
    cookiej = CookieJar() #创建cookie对象
    handler = request.HTTPCookieProcessor(cookiej)
    #使用cookiejar创建HTTPCookieProcess对象
    opener = request.build_opener(handler) #使用handler创建一个opener
    return opener

#登录
def login_renren(opener):
    data = {'username':'...','password':'...'}#登录信息
    login_url = 'http://name.renren.com/log?key=pv-www|browser-chrome&val=1|83'
    req = request.Request(login_url,data=parse.urlencode(data).encode('utf-8'),headers=headers)
    opener.open(req)

#访问
def visit_profile(opener):
    dapeng_url = 'http://www.renren.com/880151247/profile'
    req = request.Request(dapeng_url,headers=headers)
    resp = opener.open(req)
    with open('renren.html','w',encoding='utf-8') as fp:
    fp.write(resp.read().decode('utf-8'))

if __name__ == '__main__':
    opener = get_opener()
    login_renren(opener)
    visit_profile(opener)#获取主页时,使用之前的opener,不要新建,因为之前的opener才含有cookie信息

 

requests库

# 代理服务器,(开放代理、讯代理),选用高匿名
import requests
proxy = {'http': 'ip:port'}         # proxy = {协议:'IP:Port'}创建代理服务器
resp1 = requests.get('http://httpbin.org/ip', proxy)      # 以代理服务器访问url
print(resp1.text)        # 获取响应文本
print(resp1.cookies.get_dict())  # 获取响应cookie

# 登录
data = {'email': '[email protected]', 'password': 'mjj57657390'}    # 登录信息
headers = {'User-Agent': '...'}
session = requests.Session()
session.post(url, data=data, headers=headers)     # 以port方式传递数据给url登录
resp2 = session.get('http://www.renren.com/880151247/profile')

# 下载
with open('renren.html', 'w', encoding='utf-8') as fp:
    fp.write(resp2.text)        # 下载响应的文本到本地html文件

 

beautiful soup库

BeautifulSoup 将 HTML 中的所有结构和节点分解为四类对象:

     Tag 指HTML中的一个个标签

     NavigableString 指HTML中的字符对象

     BeautifulSoup 约等于Tag对象,用于生成BeautifulSoup树

     Comment 注释内容​

from bs4 import BeautifulSoup
html = '...网页文本内容...'
soup = BeautifulSoup(html, 'lxml')
print(soup.find_all('span'))                # 获取所有 span 标签
print(soup.find_all('span', limit=2)[1])    # 只获取前两个 span 标签,打印第2个
# 注意:etree 的列表从1开始,soup 的列表从0开始
print(soup.find_all('span', attrs={'class': "share-text"}))   
# 获取所有属性 class=“share-text” 的 span 标签
print(soup.find_all('span', attrs={'class': "share-text", 'data-v-114c7c2f': ""}))
# 多属性标签查找
spans = soup.find_all('div')
for cl in spans:
    print(cl.attrs['class'])                # 获取所有 div 的 class 属性

alist = soup.find_all('a', attrs={'class': 'recruit-list-link'})  # 获取职务信息
posi_list = []
for a in alist:
    dict = {}
    dict['position'] = a.find_all('h4', attrs={'class': 'recruit-title'})[0].string
    # 获取信息中的字符串成分<>str<>
    dict['address'] = a.find_all('span', attrs={'data-v-288d7ecc': ''})[1].string
    dict['detail'] = a.find_all('p', attrs={'class': 'recruit-text'})[0].string
    posi_list.append(dict)
print(posi_list)

for a in alist:
    infos = list(a.stripped_strings)        
    # a.strings 可以将所有非网页格式的字符元素提取出,返回生成器,可转换为列表
    # a.stripped_strings 可以将所有非网页格式的非空字符元素提取出
    for info in infos:
        if info.startswith('|'):     # 筛选出不符合的字符
            continue
        else:
            print(info)

lxml库

利用lxml库解析HTML代码:

     解析HTML字符串:使用 lxml.etree.HTML 进行解析,line34, line36

     解析HTML文件:   使用 lxml.etree.parse 进行解析,这个函数默认使用 XML解析器,当遇到不规范代码时会解析错误,因此需要自己创建 HTML解释器,line41-43​

from lxml import etree

text = """...HTML字符串..."""
def parse_text():
    htmlElement = etree.HTML(text)              # 创建HTML对象
    print(type(htmlElement))                    # htmlElement类型
    print(etree.tostring(htmlElement, encoding='utf-8').decode('utf-8'))   
    # text方法获取网页内容

def parse_file(html_file):
    parser = etree.HTMLParser(encoding= 'utf-8')                                   
    # 创建解释器,防止网页中不规范代码引起错误
    htmlElement = etree.parse(html_file, parser=parser)
    # 以自定义解释器,创建HTML对象
    print(etree.tostring(htmlElement, encoding='utf-8').decode('utf-8'))   
    # file方法获取网页内容,转换为便于阅读的格式(先编码,后解码)

if __name__ == '__main__':
    parse_text()
    print('-----'*30, '\n\n', '-----'*30)
    parse_file('renren.html')                   # 传参(此处相对路径)

json库

json (JavaScript Object Notation) 是一种轻量级的数据交换格式,支持 字典、列表、整形、浮点、布尔、null、字符串(必须使用双引号),多个数据用逗号分开 (详情查询 json.cn) 
    json.dump       非文件,将Python加载为json 
    json.dumps      将Python加载为json文件 
    json.load          非文件,将json加载为Python 
    json.loads         将json加载为Python文件

import json

# 将Python对象转换为json对象
persons1 = [{'user-name': '杰瑞', 'age': '18', 'country': 'china'},
            {'user-name': 'tom', 'age': '20', 'country': 'china'}]
json_str1 = json.dumps(persons1)                   # 加载,dumps不写入文件
print(type(json_str1))
print(json_str1)               # 可将输出结果放到 json.cn 进行检查
with open('persons.json1', 'w') as fp:
    # fp.write(json_str)
    json.dump(persons1, fp)    # dump写入文件

with open('persons.json2', 'w', encoding='utf-8') as fp:
    # fp.write(json_str)
    json.dump(persons1, fp, ensure_ascii=False)    # 保证中文能正常显示

# 将json加载为Python对象
json_str2 = '[{"user-name": "汤姆", "age": "1", "country": "china"}, {"user-name": "shabi", "age": "25", "country": "china"}]'
persons2 = json.loads(json_str2)    # 下载,loads不涉及文件
print(type(persons2))
for person in persons2:
    print(person)

with open('persons.json2', 'r', encoding='utf-8') as fp:
    persons3 = json.load(fp)        # 下载,load从文件中取出
    print(type(persons3))
    for person in persons3:
        print(person)

csv库

csv 常用于转移程序表格数据
    csv.reader 读取csv文件 
    csv.writer 写入csv文件

import csv
headers = ['student', 'age', 'grade']
values = [('张三', 18, 60), ('golf', 20, 59), ('jennie', 17, 99)]
dicts = [{'student': '张三', 'age': 18, 'grade': 60},
         {'student': 'golf', 'age': 20, 'grade': 59},
         {'student': 'jennie', 'age': 17, 'grade': 99}]

def write_csv_way1(headers, values):    # 写入CSV文件方法1,分别写入
    with open('classroom.csv', 'w', encoding='utf-8', newline='') as fp:    
    # encoding防止中文乱码,newline避免默认\n产生额外换行
        writer = csv.writer(fp)
        writer.writerow(headers)
        writer.writerows(values)

def write_csv_way2(headers, dicts):     # 写入CSV文件方法2,以字典写入
    with open('classroom.csv', 'w', encoding='utf-8', newline='') as fp:    
    # encoding防止中文乱码,newline避免默认\n产生额外换行
        writer = csv.DictWriter(fp, headers)
        writer.writeheader()
        for dict in dicts:
            writer.writerow(dict)

def read_csv_way1():                    # 读取CSV文件方法1,按索引读取
    with open('classroom.csv', 'r', encoding='utf-8') as fp:
        reader = csv.reader(fp)         # reader是一个迭代器
        next(reader)                    # 跳过第一行标题栏
        for x in reader:
            print(x)
            print(f'age:{x[1]}')        # 通过索引查找

def read_csv_way2(title):               # 读取CSV文件方法2,按键值对读取
    with open('classroom.csv', 'r', encoding='utf-8') as fp:
        reader = csv.DictReader(fp)     # 使用DictReader不会包含标题的数据
        for x in reader:
            print(x)
            print(f'name:{x[title]}')   # 按照标题key进行查找

write_csv_way1(headers, values)
write_csv_way2(headers, dicts)
read_csv_way1()
read_csv_way2('student')

常用爬虫框架

requests响应分析

    最为简便的爬虫,用于个人的小型爬取,常常可将beautifulsoup、re、xpath等结合使用,可以随时执行爬取,结果实时产生,当数据量过大或者涉及其他保存格式,建议采用结构完善的scrapy框架来设置中间介、多线程、数据库、数据分类以及其他操作

scrapy爬虫框架

    最为广泛的爬虫框架,可用于爬取绝大多数网页绰绰有余,具有完善的爬虫框架,可根据不同的情况进行参数自定义修改,支持数据库、多线程、分布式、中间介以及其他设置,唯一的缺点需要自定义启动模块或者通过cmd启动,且编写时对于爬虫结果很难实时查看,不方便代码的修改和完善。所以往往编写爬虫代码时,可结合requests方法进行结果验证和代码修改

 

scrapy框架 
        通过 pip install scrapy 安装 
        通过 pip 方式下载安装 pypiwin32 
        命令行 where python 查看 python 的版本和位置 
        通过 pip 下载安装 twisted,如果失败则改为手动安装,从网上下载与 py 对应版本(包括版本和操作系统)的安装包,并放到 python 安装目录下的 Scripts 文件夹里 
        通过 pip 安装路径下的 whl 文件 
        通过 pip 下载安装 scrapy 
        安装教程: https://blog.csdn.net/wsgjcxy13/article/details/88218931 
 中文文档:http://scrapy-chs.readthedocs.io/zh_CN/latest/index.html 

创建 scrapy 爬虫 
        在你想要的文件夹下打开命令行 scrapy startproject spider_name 即可创建一个包,包的名字自定义 
        通过pycharm可以打开查看这个包,包括了一系列的爬虫文件: 
            items.py                  用于存储爬取数据的模型 
            middlewares.py      用于存放各种中间件 
            pipelines.py            用于将items的模型存储到本地磁盘 
            settings.py              本爬虫的一些配置信息( 
            scrapy.cfg               项目配置文件 
            spiders包                存放爬虫 
        在创建的包下,通过命令行 scrapy genspider spider_name "url" 爬虫名字自定义,但是不可与包名重复,并填写目标网站域名 url 
        配置 setting 参数 
            ROBOTSTXT_OBEY = False              否认机器人协议 
            DEFAULT_REQUEST_HEADERS = {'Accept':..., 'Accept-Language':..., 'User-Agent':...}                                                                      请求头伪装 
            ITEM_PIPELINE = {...}                        导出保存 
        在创建的包下,通过命令行 scrapy crawl 爬虫文件.py 执行爬虫


    response 是一个 scrapy.http.response.html.HtmlResponse 对象,可以执行 xpath 和 css 语句来提取数据,提取出来的数据,是一个 Selector 或者 SelectorList 对象, 
    如果想要字符串,执行 getall 或者 get 方法 
            getall 获取 Selector 中的所有文本,返回一个列表 
            get 获取 Selector 中的第一个文本,返回一个 str 类型 
    如果数据解析回来,要传给 pipline 处理,可以使用 yield 返回生成器(或者可以提前创建空列表,然后进行返回) 
    pipline 专门用于保存数据,包括三个常用方法: 
            open_spider(self, spider):                当爬虫被打开时执行 
            process_item(self, item, spider):      当爬虫有 item 传回时会被调用 
            close_spider(self, spider):                当爬虫关闭时会被调用 
    要** pipline 应该在 settings.py 中设置 ITEM_PIPLINES 


CrawlSpider 爬虫 
创建:命令行执行 
页面规则:使用LinkExtractor和Rule函数,利用正则决定爬虫走向 
                 follow为True,则对所有满足正则页面进行跟进 
                 callback,若只是为了依据当前url获取更多深度url则可以不指定,若需要对满足正则页面进行数据爬取,则执行一个callback 


ScrapyShell 
在爬虫项目执行命令行 scrapy shell "目标url",可以执行python代码,例如 import, xpath, beautifulsoup, 查看(直接变量+回车,不用print) ,便于代码修改完善


LinkExtractors 链接提取器 
        allow                  所有满足正则的url都会被提取 
        deny                   所有满足正则的url都不会被提取 
        allow_domains   指定域名的url会被提取 
        deny_domains    指定域名的url不会被提取 
        restrict_xpaths    严格的xpath,与allow配合使用 


JsonItemExporter & JsonLinesItemExporter 
JsonItemExporter:每次把数据添加到内存中,最后统一写入磁盘 
        优点:json数据规则,以大列表 [{},{},{}...] 格式的json文件 
        缺点:当数据量比较大时,内存需求大,需要通过 finish 手动关闭 
JsonLinesItemExporter:每次调用exporter_item就会写入磁盘 
        优点:内存需求少,数据安全 
        缺点:每一个字典独立,json文件格式不规范 


Request类相关参数: 
        url:             发送请求的域名 
        callback:    在数据下载完成后,执行回调 
        method:    请求的方法,常用 GET、Post 
        headers:    请求头,用于设置请求信息,在 settings.py 文件中指定即可 
        meta:         不同请求之间传递数据 
        encoding:  编码方式,常用 utf-8、gbk 
        dot_filter:   调度器过滤,发送重复请求,例如验证码 
        errback:     发生错误时执行的函数 


Response类相关参数: 
        meta:         不同请求之间传递数据 
        encoding:  编码方式,常用 utf-8、gbk 
        text:           经过解码后的 Unicode 字符串 
        body:         以 byte 字符串返回 
        xpath:        选择器 
        css:            选择器 


发送Post请求 
字母验证码识别:阿里云市场-图片验证码识别 进行购买和学习 


pipeline下载模式 

Files Pipeline下载文件: 
        1、定义好 Item,以及它的两个属性,分别为 file_urls(用于存储文件所在链接,列表格式)以及 files() 
        2、文件下载完成后,会将相关信息存储到 Item 的 files 属性中。例如下载路径、url、文件校验码等 
        3、在 settings.py 中配置 FILES_STORE,用于设置下载至的路径(需要os模块) 
        4、启动 pipeline,在 ITEM_PIPELINES 中设置 scrapy.pipelines.files.FilesPipeline:1 

Image Pipeline下载图片: 
        1、定义好 Item,以及它的两个属性,分别为 image_urls(用于存储图片链接,列表格式)以及 images 
        2、文件下载完成后,会将相关信息存储到 Item 的 images 属性中。例如下载路径、url、图片校验码等 
        3、在 settings.py 中配置 IMAGES_STORE,用于设置下载至的路径(需要os模块) 
        4、启动 pipeline,在 ITEM_PIPELINES 中设置 scrapy.pipelines.images.imagesPipeline:1

 

Redis分布式爬虫

    往往用于多ip合作,将爬取内容按规则存储到共用的数据库,提高爬虫编写、执行、下载的效率,避免数据重复,方便错误查找修正

    此处暂时未对其研究

 

爬虫实战

以下爬虫均为 2020/5-2020/7 期间有效执行,基于python3.8以及项目所需的对应三方库

不排除此后代码失效的情况,可能来自于python及相关插件的升级,或者网页源代码优化

电影详情爬虫

基于requests和lxml库,对电影天堂进行影片详情爬取

"""
电影天堂影片爬取
分页爬取
详情爬取

    1、获取电影天堂的热门电影页面,获得 url
    2、遍历第一页所有电影,获得他们的详情页链接
    3、获取详情页链接中的电影信息
    4、观察每一页的url规律,遍历所有页,得到所有电影的详情信息
    5、分别将 获取电影页链接、获取电影详情信息、各项详情格式调整 等定义成函数以便调用,节省代码量
    6、主函数完成 所有电影页、每页所有电影 的遍历查询,通过调用函数实现爬虫功能
"""
import requests
from lxml import etree

# 定义全局变量
Base_Domain = 'https://www.dytt8.net'
Headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/83.0.4103.97 Safari/537.36'}


# 电影详情格式调整
def parse_info(info, rule):  # 删除各项信息的名字头
    return info.replace(rule, '')


def list_dealing(list, style):  # 删除列表中的无效空格,调整乱码
    new_list = ''
    for element in list:
        if '\r\n' in element:
            continue
        else:
            element = element.replace('\u3000', '', 50)
            element = element.replace('\xa0', ' ', 50)
            element = element.replace('\r\n', '', 50)
            if style == 1:
                new_list += element.strip() + '\n'
            elif style == 2:
                new_list += element + ' '
            else:
                new_list += element
    return new_list.strip()


# 获取电影详情
def get_detail(detail_url):
    resp = requests.get(detail_url, headers=Headers)
    text = resp.content.decode('gbk')
    html = etree.HTML(text)
    name = html.xpath("//div[@class='title_all']//font/text()")[0]
    print(name)
    zoom = html.xpath("//div[@id='Zoom']/span[@style]")[0]
    img = zoom.xpath("//img/@src")[0]
    print(img)
    infos = html.xpath("//div[@id='Zoom']//text()")

    for index, info in enumerate(infos):
        if info.startswith('◎片\u3000\u3000名'):                  # 获得片名
            print(parse_info(info, '◎片\u3000\u3000名\u3000'))
        elif info.startswith('◎译'):                              # 获得译名
            print(parse_info(info, '◎译\u3000\u3000名\u3000'))
        elif info.startswith('◎上映日期'):                        # 获得上映日期
            print(parse_info(info, '◎上映日期\u3000'))
        elif info.startswith('◎豆瓣评分'):                        # 获得豆瓣评分
            print(parse_info(info, '◎豆瓣评分\u3000'))
        elif info.startswith('◎主\u3000\u3000演'):                # 获得主演列表
            actors = [info.replace('◎主  演 ', '')]
            for x in range(index + 2, len(infos)):
                actor = infos[x]
                if actor.startswith('◎'):
                    break
                actors.append(actor.replace('  ', ''))
            actor_list = list_dealing(actors, 1)
            print(actor_list)
        elif info.startswith('◎简'):                              # 获得简介
            abstract = []
            for x in range(index + 2, len(infos)):
                profile = infos[x]
                if profile.startswith('【下载地址】'):
                    break
                abstract.append(profile)
            print(list_dealing(abstract, 3))
        elif info.startswith('【下载地址】'):                     # 获得下载地址
            downloads = []
            for x in range(index + 2, len(infos)):
                download = infos[x]
                if download.startswith('◎'):
                    break
                downloads.append(download)
            print(list_dealing(downloads, 2))
        else:
            continue
    # print(infos)                             # 电影详情总列表,便于查看和修改


# 获取详情页链接
def get_url(url):
    resp = requests.get(url, headers=Headers)
    text = resp.text
    html = etree.HTML(text)
    ul = html.xpath("//table[@style]//a/@href")  # 获取某一页某一个电影的详情链接
    url_list = map(lambda url: Base_Domain + url, ul)  
    # 遍历每一页的电影列表获得链接(map是遍历列表,并对其进行函数操作)
    # 相当于:
    #   for detail_url in ul:
    #       return Base_Domain + detail_url
    # print(list(url_list))
    return list(url_list)


# 主函数
if __name__ == '__main__':
    for i in range(215):                   # 遍历所有页获得链接,共215
        url = 'https://www.dytt8.net/html/gndy/dyzz/list_23_' + str(i + 1) + '.html'
        for detail_url in get_url(url):  # 遍历该页所有电影获得详情链接
            # print(detail_url)
            get_detail(detail_url)
            break                        
            # 做测试时,可以添加 break 来跳出循环,只获取一个电影详情
        break

# get_detail(https://www.dytt8.nethttps://www.dytt8.net/html/gndy/dyzz/list_23_1.html)

城市天气爬虫

基于requests和beautifulsoup库,对中国天气网所有城市天气信息进行爬取

"""
中国天气网-全国所有城市天气信息爬虫程序
    1、分析网页结构
    2、找出关键字标签
    3、Python爬虫检索关键字获得结果
    4、对爬取的信息进行删减调整
    5、修改bug,调试程序
    6、pyecharts 的数据可视化
注意:
    1、获取网页代码文本所采用的编译器,优先考虑 .text 若出现乱码,则改用 .content.decode('自定义解码方式'),
       解码方式视网页中 head 标签下 charset 属性而定。一般为'utf-8'或'gbk'
    2、如果采用 BeautifulSoup 方式进行检索,优先采用速度较快的 lxml 方式,
       如果出现问题或者无法识别,则改用兼容性更好的 html5lib
    3、编写程序时,可以通过打印相关项或者其 type 来进行研究分析。
       在遍历时,可以先通过 break 检查第一个循环是否正常
    4、对于一些需要定义的简单函数,可通过 lambda 函数进行简化代码
"""
import requests
from bs4 import BeautifulSoup
from pyecharts.charts import Bar


def url_get(str):
    url = "http://www.weather.com.cn/textFC/" + str + ".shtml"
    return url


def province_dealing(province):
    if province == '北京' or province == '天津' or province == '上海' or province == '重庆':
        return province + '市'
    else:
        return province + '省'

def parse_page(url):
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
               'Referer': 'http://www.weather.com.cn/forecast/'}
    resp = requests.get(url, headers=headers)
    text = resp.content.decode('utf-8')
    # print(text)                              # 便于检查及修改
    soup = BeautifulSoup(text, "html5lib")     
    # lxml 无法识别缺失的标签,应采用兼容性更好的 html5lib
    conMidtab = soup.find('div', attrs={'class': "conMidtab"})
    tables = conMidtab.find_all('table')
    for table in tables:
        # print(table)                         # 便于检查及修改
        # print("="*50)
        trs = table.find_all('tr')[2:]
        province = list(table.find_all('td', attrs={'width': '74'})[1].stripped_strings)[0]  # 省份

        for tr in trs:
            # print(tr)                        # 便于检查及修改
            # print("="*50)
            tds = tr.find_all('td')
            i = 1 if tr.find_all('td', attrs={'width': '74'}) else 0

            city = list(tds[0 + i].stripped_strings)[0]                # 城市
            weather_d = list(tds[1 + i].stripped_strings)[0]           # 白天天气
            wind_d = list(tds[2 + i].stripped_strings)[0]              # 白天风向
            power_d = list(tds[2 + i].stripped_strings)[1]             # 白天风力
            temp_max = list(tds[3 + i].stripped_strings)[0]            # 最高气温
            weather_n = list(tds[4 + i].stripped_strings)[0]           # 晚上天气
            wind_n = list(tds[5 + i].stripped_strings)[0]              # 晚上风向
            power_n = list(tds[5 + i].stripped_strings)[1]             # 晚上风力
            temp_min = list(tds[6 + i].stripped_strings)[0]            # 最低气温

            # print(type(province))            # province属于生成器generator
            # print(f'{province_dealing(province)}{city}最低气温为{temp_min}摄氏度')    # 观察打印结果是否正常
            all_data.append({'city': city, 'temp_min': int(temp_min)})
            # break                              # 便于检查及修改
        # break                                  # 便于检查及修改


zones = ['hb', 'db', 'hd', 'hz', 'hn', 'xb', 'xn', 'gat']
all_data = []
for zone in zones:
    parse_page(url_get(zone))
# all_data = [{'city': '北京', 'temp_min': 0}, {'city': '天津', 'temp_min': 2}, {'city': '重庆', 'temp_min': 16}]   # 得到数据格式模板
all_data.sort(key=lambda data: data['temp_min'])       
# 通过 lambda 函数,根据最低气温,对列表元素进行正向排序
# print(all_data)
rank = all_data[0: 10]

cities = map(lambda x: x['city'], rank)
temps = map(lambda y: y['temp_min'], rank)
# print(list(cities))

chart = Bar()
chart.add_yaxis(series_name='最低气温', yaxis_data=list(temps))
chart.add_xaxis(xaxis_data=list(cities))
chart.render('temp.html')

 

古诗词爬虫

基于requests库和正则表达式re,对中国古诗词网推荐诗词进行信息爬取

import requests
import re

headers = {'user-agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
           'referer': 'https://www.gushiwen.org/'}


def parse_url(url):
    resp = requests.get(url, headers=headers)
    text = resp.text

    titles = re.findall(r'<div\sclass="cont">.*?<b>(.*?)</b>', text, re.DOTALL)
    # <div\sclass="cont">.*<b>(.*)</b>  若不加?号,贪婪模式,则会以最长字符串进行匹配,输出会包含其中所有无关项
    # re.DOTALL 可以让 . 能够匹配所有字符,包括换行符
    dynasties = re.findall(r'<p class="source">.*?<a.*?>(.*?)</a>', text)
    authors = re.findall(r'p class="source">.*?<span>.*?</span><a.*?>(.*?)</a>', text)
    contents_Tag = re.findall(r'<div class="contson" .*?>(.*?)</div>', text, re.DOTALL)

    contents = []
    for content in contents_Tag:
        content = re.sub(r"<.*?>", '', content).strip()
        contents.append(content)

    poems = []
    for value in zip(titles, dynasties, authors, contents):
        title, dynasty, author, content = value
        poem = {'题目:': title, '朝代:': dynasty, '作者:': author, '诗文': content}
        print(poem)
        poems.append(poem)


for x in range(1, 11):
    url = "https://www.gushiwen.org/default_" + str(x) + ".aspx"
    parse_url(url)

 

拉勾网职位爬虫

基于selenium、re、lxml库,由于拉勾网反爬虫机制和AJAX渲染,借助浏览器自动化测试方法,模拟用户鼠标操作,获取目标信息

"""
拉勾网爬虫
拉勾网的反爬虫机制非常严格,存在临时 cookie、随机变量、全屏广告以及图形验证码,会直接让爬虫失效
从 selenium 的角度模拟浏览器操作,可以避免被识别为爬虫,但是效率会大幅下降,每次爬取都需要等待网页页面加载
本程序虽然可以完美跑通,但是当爬取到第10页时需要登录,并且需要图形验证码,因此实际上只能爬取到前10页内容

"""
from selenium import webdriver
from lxml import etree
import re
import time
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

driver_path = r"C:\Users\Fally J\Desktop\python tofun\chromedriver.exe"           
# 浏览器自动化测试驱动路径


class LagouSpider(object):
    def __init__(self):                                                           
        # 定义类属性
        self.driver = webdriver.Chrome(executable_path=driver_path)
        self.url = 'https://www.lagou.com/jobs/list_python?labelWords=&fromSearch=true&suginput='
        self.position_details = []

    def run(self):
        self.driver.get(self.url)  # 获取页面
        ads_btn = self.driver.find_element_by_xpath("//div[@class='body-container showData']//div[@class='body-btn']")
        ads_btn.click()  # 关闭某个全屏广告,否则无法实现下面的翻页操作
        while True:
            source = self.driver.page_source
            WebDriverWait(driver=self.driver, timeout=10).until(EC.presence_of_all_elements_located)   # 等待加载
            self.parse_list_page(source)
            next_btn = self.driver.find_element_by_xpath("//div[@class='pager_container']/span[last()]")
            if "pager_next_disabled" in next_btn.get_attribute("class"):          
                # 当到达最后一页时,停止运行
                break
            else:
                next_btn.click()             # 点击翻页
            time.sleep(2)                    # 翻页停留,避免被识别为爬虫

    def parse_list_page(self, source):
        html = etree.HTML(source)
        links = html.xpath("//a[@class='position_link']/@href")   # 获取详情页链接
        for link in links:             # 遍历当前页的所有职位详情
            self.request_detail_page(link)
            time.sleep(0.3)
            print(link)

    def request_detail_page(self, url):
        self.driver.execute_script("window.open('%s')" % url)    # 打开详情页
        self.driver.switch_to.window(self.driver.window_handles[1])           
        # driver 切换至详情页进行操作
        WebDriverWait(driver=self.driver, timeout=10).until(
            EC.presence_of_element_located([By.XPATH, "//h1[@class='name']"]))    
            # 等待详情加载
        source = self.driver.page_source
        self.parse_detail_page(source)
        self.driver.close()      # 关闭当前详情页
        self.driver.switch_to.window(self.driver.window_handles[0])               
        # driver 切换回职位列表页面

    def parse_detail_page(self, source):           # 爬取各详细信息
        html = etree.HTML(source)
        position_name = html.xpath("//h1[@class='name']/text()")[0]
        company = html.xpath("//h3[@class='fl']/em[@class='fl-cn']/text()")[0].strip()
        job_request_spans = html.xpath("//dd[@class='job_request']//span//text()")
        salary = re.sub(r"[\s/]", "", job_request_spans[0].strip())
        city = re.sub(r"[\s/]", "", job_request_spans[1].strip())
        work_years = re.sub(r"[\s/]", "", job_request_spans[2].strip())
        education = re.sub(r"[\s/]", "", job_request_spans[3].strip())
        desc = "".join(html.xpath("//dd[@class='job_bt']//text()")).strip()
        position_details = {
            'name': position_name,
            'company': company,
            'salary': salary,
            'city': city,
            'work_years': work_years,
            'education': education,
            'desc': desc
        }
        self.position_details.append(position_details)
        print(position_details)
        print('='*50)


if __name__ == '__main__':
    spider = LagouSpider()                                        # 创建类
    spider.run()                                                  # 启动爬虫
    print(spider.position_details)

表情包爬取

基于requests、urllib、re、queue、lxml、threading库,对热门表情包进行链接爬取并下载至本地,通过多线程(生产者和消费者)进行高效爬取和下载

import requests
import threading
from lxml import etree
import os
from queue import Queue
import re
from urllib import request

""" 多线程爬虫 """

# 生产者:获取 page_queue 中的 URL,经过爬虫获得信息,并送到 img_queue中
class Producer(threading.Thread):                 
    Headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36',
           'Referer': 'https://www.doutula.com/photo/list/?page=2'}

    def __init__(self, page_queue, img_queue, *args, **kwargs):        
        # 添加参数,重新编写 __init__
        super(Producer, self).__init__(*args, **kwargs) # 继承原有参数
        self.page_queue = page_queue                    # 附加参数
        self.img_queue = img_queue

    def run(self):
        while True:
            if self.page_queue.empty():
            # 当 URL 队伍清空时,生产者获取信息线程结束
                break
            url = self.page_queue.get()
            self.parse_url(url)

    def parse_url(self, url):                          # 爬虫检索获取信息
        resp = requests.get(url, headers=self.Headers)
        text = resp.text
        html = etree.HTML(text)
        imgs = html.xpath("//div[@class='page-content text-center']//img")
        for img in imgs:
            img_url = img.get('data-original')
            name = img.get('alt')
            name = re.sub(r'[\??\.,。!!*]', '', name)  # 去除特殊字符,避免报错
            suffix = os.path.splitext(img_url)[1]      # 获取后缀名
            filename = name + suffix                   # 合并成正确的文件格式
            self.img_queue.put((img_url, filename))    
            # 添加至 img_queue 队列等待消费
            print(f'成功获取{filename}')
            # print(name)
            # print(img_url)
            # print(suffix)
            # print(filename)

# 消费者:获取 img_queue 中的链接和文件名,进行下载操作
class Consumer(threading.Thread):                 
    def __init__(self, page_queue, img_queue, *args, **kwargs):
        super(Consumer, self).__init__(*args, **kwargs)
        self.page_queue = page_queue
        self.img_queue = img_queue

    def run(self):
        while True:
            if self.img_queue.empty() and self.page_queue.empty():        
            # 当两个队列都清空了,则消费者可结束线程
                break
            img_url, filename = self.img_queue.get()
            print(f"成功下载了{img_url},命名为{filename}")
            request.urlretrieve(img_url, 'biaoqingbao/%s' % filename)     
            # 下载图片至本地

if __name__ == '__main__':
    page_queue = Queue(100)
    img_queue = Queue(100)
    for i in range(1, 10):            # 遍历所有网页
        url = 'https://www.doutula.com/photo/list/?page=%d' % i
        page_queue.put(url)
        # break
        
    # 区分生产者和消费者,保证先批量完成一部分生产,避免虚假结束
    for i in range(5):                                      
        tp = Producer(page_queue, img_queue)
        tp.start()
    for i in range(5):
        tc = Consumer(page_queue, img_queue)
        tc.start()

简书爬虫

基于scrapy框架,是较为综合的一个爬虫,包含的爬虫知识众多,scrapy框架也属于常用爬虫框架,建议反复学习。

由于scrapy框架下的爬虫保存于一个包中,详细程序及步骤见附件:

 

 

爬虫总结

1、页面分析:对于目标页面的构成、源代码、渲染方式有目的的解析,明确目标爬取信息和爬取范围

2、请求:务必设置请求头、代理、请求方式甚至cookie,以防止反爬虫机制或者ip限制造成不必要的麻烦,分析NETWORK信息中带x的信息,可能是用于反爬虫而随机生成的请求头验证信息

3、响应:分析响应是否正确(可能是被网页识别而传回的虚假信息)、是否缺失信息(JS或AJAX渲染导致源代码信息不全)、是否乱码(编码方式需要ELEMENT中查询或尝试)、是否信息对应(对于json数据往往可能不是字典直接对应)

4、干扰:页面是否有AJAX渲染(可能涉及到信息主动查找或者自动化测试驱动)、页面反爬虫(可能需要登录、验证码、cookie甚至随机请求头)

5、信息提取:通常采用xpath或者正则,通过xpath进行位置锁定,通过正则进行信息提取,注意xpath筛选出的无效信息、正则贪婪模式产生无效信息

6、信息下载:对于图片、音频等较大文件,建议采用生产者消费者提高效率,对于数据库下载,注意各项信息对应

7、代码:采用scrapy框架,由requests配合,可以在保证爬虫框架完整性的同时,提高编写效率,方便修改调整,便于后期添加更改相关参数和设置。个人不建议采用beautifulsoup形式,因为速度慢、语法复杂。建议使用xpath配合re,使用习惯后屡试不爽

相关标签: python 笔记