python爬虫 笔记

不使用框架

urllib的基本使用

urllib.request(url) 返回你的 http.client.HTTPResponse 文件的基本信息

import urllib.request as request

# 给定url
url = 'https://www.baidu.com/'

# 模拟游览器像服务器发送请求
#  urlopen 返回的是 http.client.HTTPResponse 类型的文件
response = request.urlopen(url)

# read()方法是读取二进制的字节数据,也就是说a是bytes类型的
#  所以需要将这个数据进行解码成str格式
#    read()里面填5代表只读取5个字节也就是5个字符,啥都不写就是全部读取
content = response.read(5)
content = content.decode('utf-8')

# getcode()方法返回运行的状态,比如404、200啥的
status = response.getcode()

# geturl()返回这个请求的url地址
re_url = response.geturl()

# getheaders()获取头文件状态信息
headers = response.getheaders()

使用 request.retrieve(url, file_name) 下载指定链接的文件,可以是整个html文件也可以是单个的图片

import urllib.request as request

url = 'https://pic1.zhimg.com/v2-5b366a0d95f96f14c26263c542df3f6b_xll.jpg'
request.urlretrieve(url, '赵今麦.jpg')  # retrieve的意思就是 “在计算机上检索”

urllib.request.Request() 伪装头文件、urllib.request.qoute() 对汉字进行unicode编码
urlopen()可以传入一个Request的对象,Request中可以包含待访问的url和headers头文件

import urllib.parse
import urllib.request as request

url = 'https://www.baidu.com/s?wd='

# 头文件
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) \
    Chrome/105.0.0.0 Safari/537.36'}

# 对汉字进行编码
name = urllib.parse.quote('赵今麦')  # 编码后就是%E8%B5%B5%E4%BB%8A%E9%BA%A6

#  在百度中搜索name关键词
url += name

# 创建Request文件将头文件和带访问的url放进去
m_request = request.Request(url=url, headers=headers)

response = request.urlopen(m_request)

# 对返回的文件进行读取并解码从而获取内容
content = response.read().decode('utf-8')
print(content)

url中可能会涉及到各种参数啥的,具体符号和参数的含义见:url中各个符号的含义
当参数很多时可以使用 urllib.parse.urlencode() 来进行编码

url = 'https://www.baidu.com/s?'  # ?后面就是接各种参数和值
# 各种参数和值
data = {
    'wd': '赵今麦',
    'sex': '女'
}

# 对所有的参数进行编码
name = urllib.parse.urlencode(data)  # 编码后就是 wd=%E8%B5%B5%E4%BB%8A%E9%BA%A6&sex=%E5%A5%B3

# 在百度中搜索name关键词
url += name

post请求比get请求在Request文件的创建中多了一个data参数,切记post的这个data需要先urlencode()再encode编码成byte类型

import urllib.parse

data = {
    'cname': '北京'
}
data = urllib.parse.urlencode(data)  # cname=%E5%8C%97%E4%BA%AC
print(data)
data = data.encode('utf-8') # b'cname=%E5%8C%97%E4%BA%AC'
print(data)

肯德基官网post+ajax的使用
ajax就是一种可以在不重新加载整个网页的前提下对网页进行部分更新的技术,当在请求标头中看到 XMLHttpRequest 时就代表使用了ajax技术

import urllib.parse
import urllib.request
import json

url = 'http://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=cname'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
}

# data可以在载荷->表单数据中查看
data = {
    'cname': '宣城',
    'pid': '',
    'pageIndex': '1',
    'pageSize': '10',
}

data = urllib.parse.urlencode(data).encode('utf-8')

m_request = urllib.request.Request(url=url, data=data, headers=headers)
response = urllib.request.urlopen(m_request)  # urlopen可以直接传入Request对象!
content = response.read().decode('utf-8')

content = json.loads(content)
print(content)

handler 以及代理的使用

当同一ip高频词的访问同一网址时可能会被封,因此可以使用代理ip来换本地ip,代理必须要用到handler对象和build_opener对象

import urllib.request

url = 'http://www.baidu.com/'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
    AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
}
# 定制Request文件
m_request = urllib.request.Request(url=url, headers=headers)
# 定义代理ip
# 代理池,将所有的代理ip放进一个列表proxies_pool中,然后random.choice(proxies_pool),就会随机返回一个ip
proxies_pool = [{'http': '117.141.155.244:53281'},{'http': '117.141.155.294:5381'}]
import random
proxies = random.choice(proxies_pool) #  注意后面有端口号
# 实例化handler对象
handler = urllib.request.ProxyHandler(proxies=proxies)
# 实例化opener对象
opener = urllib.request.build_opener(handler)
# 直接通过opener对象访问网页,而不是urlopen!
response = opener.open(m_request)
content = response.read().decode('utf-8')
with open('baudu.html', 'w', encoding='utf-8') as fp:
    fp.write(content)

Xpath的使用

安装:
首先在chrome中安装xpath Helper插件,然后到python中安装lxml库

from lxml import etree

# xpath可以解析本地的html文件也可以直接解析 content = response.read().decode('utf-8')对象
# 使用的函数是不一样的,解析本地的html文件使用etree.parse('wenjian.html')
#                  解析直接爬取的文件使用的是etree.HTML(content)
tree = etree.parse('baidu.html')
print(tree)

xpath的基本语法
xpath的使用
切记切记!爬取的网页与你在浏览器上看到的网页可能是不一样的!因为网站的建造者可能使用了 lazy加载,会慢慢的加载,但是你爬取的网页可不会等他加载结束,而是直接爬取,所以需要将网页下载到本地,用pycharm仔细看看差在哪!特别是某些参数加载前可能没有,你用那个表达式用的话就会提取不到数据!

# -*- coding: utf-8 -*-
# @Time : 2022/9/17 13:00
# @Author : Lanpangzi
# @File : xpath的基本使用.py

from lxml import etree
import urllib.request as request

# 根据链接下载的函数
main_url = 'https://sc.chinaz.com/tupian/'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) \
    Chrome/105.0.0.0 Safari/537.36'}


def get_all_img_url(page) -> tuple:
    page_url = 'index.html' if page == 1 else 'index_{}.html'.format(str(page))
    full_page_url = main_url + page_url
    m_request = request.Request(url=full_page_url, headers=headers)
    response = request.urlopen(m_request)
    content = response.read().decode('utf-8')
    tree = etree.HTML(content)
    # 加载完之后的是 //div[@class='item masonry-brick']//img/@src
    urls = tree.xpath("//div[@class='item']//img/@data-original")
    for i in range(len(urls)):
        urls[i] = 'https:' + urls[i]
    names = tree.xpath("//div[@class='item']/img/@alt")
    return urls, names


#  使用urlretrieve下载图片,urlretrieve不需要头文件啥的
def download_imgs(urls, names):
    for ind, url in enumerate(urls):
        request.urlretrieve(url, './download_files/{}.jpg'.format(names[ind]))
        print("{}/{}".format(ind + 1, len(urls)))


if __name__ == "__main__":
    start_page = int(input('请输入起始的页码:'))
    end_page = int(input("请输入结束的页码(最大为2740):"))
    for page in range(start_page, end_page + 1):
        urls, names = get_all_img_url(page)
        download_imgs(urls, names)
        print("已完成{}/{}页".format(page+1, end_page-start_page+1))

beautifulsoup jsonpath的使用

跟xpath一样是用来解析网页的

文档没保存,自己找去吧 B站尚硅谷视频

selenium

selenium官方中文文档
由于官方最近才更新大版本,很多语法变了,比如fina_elements_by 详见:
selenium更新后变化

使用驱动调用真正的浏览器进行访问,sleep用的好的话就跟真人操作一样

安装selenium:
pip install selenium
原生的selenium由于需要慢慢加载网页和渲染所以比较慢,可以使用Phantomjs实现无界面的加载,由于无界面加载不进行css和gui的渲染,会快很多

from selenium import webdriver

#  实例化,需要事先将exe文件放进同一目录中
browser = webdriver.Chrome()

url = 'https://www.baidu.com/'
# 打开实例化的浏览器
browser.get(url)
# 获取渲染之后的网页源码
page = browser.page_source

要想通过代码操作网页的自动运行,首先要能定位到网页中的每一个你想要点击的元素,这就是元素的定位
下面以用百度搜索某个关键词为例,记录如何操作selenuim定位并点击目标元素
具体操作就是在文本框中输入 “周杰伦” ,然后点击 “百度一下”

# @File : selenium学习.py

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
driver.get("http://www.baidu.com")
# 使用属性名查找
input_box = driver.find_element('name', "wd")
# 使用xpath来查找
button =driver.find_element('xpath', '//input[@id="su"]')
#  先清楚输入框中所有的预填充文本
input_box.clear()
#  在这个元素中输入要搜索的词
input_box.send_keys("周杰伦")
# 输入回车进行搜索,也可以点击按钮
#   input_box.send_keys(Keys.RETURN)
button.click()
# driver.close()

Phantomjs和Chrome headless

phantomjs 已经停止更新了,就别学了,看看chrome的”无头模式“

下面的代码就是调用chrome无头模式的固定函数,在我的电脑上路径就是这个,是死的!直接调用build_browser()函数就可以返回一个无头的browser对象

from selenium import webdriver
from selenium.webdriver.chrome.options import  Options

def build_browser():
    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    # 自己电脑上chrome.exe的地址
    path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
    chrome_options.binary_location = path
    browser = webdriver.Chrome(options=chrome_options)
    return browser
    
browser = build_browser()
url = 'https://www.baidu.com'
browser.get(url)
browser.save_screenshot('baidu.png')

requests的使用

前面用的都是python自带的urllib中的request包,下面学习的是第三方的requests包,

# -*- coding: utf-8 -*-
# @Time : 2022/9/22 23:01
# @Author : Lanpangzi
# @File : requests学习.py
import requests

url = 'http://www.baidu.com'

#   关键的是一个类型,6个属性
response = requests.get(url)
# 1类型
print(type(response))  # 

# 需要先设置response的编码格式,在查看他的text才不会乱码
#    属性1,encoding,默认的是 ISO-8859-1
print(response.encoding)
response.encoding = 'utf-8'

#    属性2:text
res_text = response.text
print(res_text)  # 直接给的就是html

#    属性3:url
res_url = response.url
print(res_url)

#   属性4:content
res_content = response.content  # 以二进制的形式返回网页数据
print(res_content)

#    属性5:status_code
res_status = response.status_code
print(res_status)  # 响应的状态码

#    属性6:headers
res_headers = response.headers
print(res_headers)  # 响应的头信息

requests.get() 的使用
requests.get()就是一个封装好了的比request更方便的函数,对于连在url后面的额一些data不需要encode

# -*- coding: utf-8 -*-
# @Time : 2022/9/22 23:01
# @Author : Lanpangzi
# @File : requests学习.py
import requests

url = 'http://www.baidu.com/s?'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
}
data = {
    'wd': '北京'
}

# requests.get()需要三个参数
#    url:请求的链接
#    params:附在url后面的参数
#    kwargs:一个字典,可以是任何urllib.request可以接受的类型
response = requests.get(url=url, params=data, headers=headers)
response.encoding = 'utf-8'
print(response.text)

request.post()的使用
post与get的区别是post请求是带着表单数据向服务器发送请求的,requests的post与上面的post的区别就是不用单独编码

import requests

#  请求资源路径,也就是下面url最后的问号,可加可不加
#  requests.get()会自动帮我们添加
url = 'http://httpbin.org/post'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
}
data = {
    'kd': 'eye'
}
response = requests.post(url=url, data=data, headers=headers)

print(response.text)

requests中代理的使用

利用代码登录网站(古诗文网)

有些网站为了反爬会用一系列的手段,这里的两个隐藏在网页中的变量就是其中之一
另外,验证码的刷新是很不寻常的,每一次请求都会刷新它,所以要用session()保持住网页的状态,而不是直接将验证码下载下来,直接下载的话会刷新验证码!

# -*- coding: utf-8 -*-
# @Time : 2022/9/25 23:17
# @Author : Lanpangzi
# @File : requests利用cookie登录古诗文网.py


# 古诗文网登录使用的是POST请求,参数如下:
# __VIEWSTATE: dn4mak43Z/re6Pv1TtFLA4OMsqjJg9AHjElin+md7cVe0sqtDv9H71/3AgAll2ePbrh9EsvMYUH13vaZR8nr+vFGsP6lgtV8dnXKh2MK9FQuhnCsvsXAWlumglH8tJX7XnkMMfVB+HkO7My08XuUpt4wGZU=
# __VIEWSTATEGENERATOR: C93BE1AE
# from: http://so.gushiwen.cn/user/collect.aspx
# email: [email protected]
# pwd: asdaasda
# code: c322
# denglu: 登录

#  __VIEWSTATE  __VIEWSTATEGENERATOR 和 code(验证码) 每一次登录都不一样
#  其他的参数是个定值

#   __VIEWSTATE  __VIEWSTATEGENERATOR 都出现再了网页的源码中,且type都是 hidden
#       所以直接解析网页,找到这两个便变量的值就好了

# 查詢__VIEWSTATE的xpath表達式為://input[@id='__VIEWSTATE']/@value
# 查詢 __VIEWSTATEGENERATOR 的xpath表達式為: //input[@id='__VIEWSTATEGENERATOR']/@value
#  至於驗證碼需要下載下來


import requests

# 登錄頁面的地址
url = 'https://so.gushiwen.cn/user/login.aspx?from=http://so.gushiwen.cn/user/collect.aspx'

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36'
}
response = requests.get(url, headers=headers)
content = response.text

# 解析页面,获得上面的两个变量
from lxml import etree

tree = etree.HTML(content)
viewstate = tree.xpath("//input[@id='__VIEWSTATE']/@value")[0]
viewstategenerator = tree.xpath("//input[@id='__VIEWSTATEGENERATOR']/@value")[0]

# 获取验证码的图片
code = tree.xpath("//img[@id='imgCode']//@src")[0]
code_url = "https://so.gushiwen.cn" + code
#  把验证码保存下来查看
from urllib import request

# 这里不能常规的将验证码下载下来,这一步操作相当于刷新验证码了!
# request.urlretrieve(url=code_url, filename='code.jpg')
# 但是可以使用requests.session()将请求变化一个对象,这样整个的程序中就只有一个一个请求,验证码不会被刷新
session = requests.session()
response_code = session.get(code_url)
content_code = response_code.content  # 不能使用text,图片需要以二进制的方式保存
with open('code.jpg', 'wb') as fp:
    fp.write(content_code)

code_name = input("请输入验证码:")

# 开始登录
url_post = "https://so.gushiwen.cn/user/login.aspx?from=http%3a%2f%2fso.gushiwen.cn%2fuser%2fcollect.aspx"
data_post = {
'__VIEWSTATE': viewstate,
'__VIEWSTATEGENERATOR': viewstategenerator,
'from': 'http://so.gushiwen.cn/user/collect.aspx',
'email': '[email protected]',
'pwd': 'asdaasda',
'code': code_name,
'denglu': '登录',
}
response_post = session.post(url=url_post, headers=headers, data=data_post)
content_post = response_post.text

with open('gushiwen.html', 'w', encoding='utf-8') as fp:
    fp.write(content_post)

使用框架

Scrapy使用

scrapy是为了方便提取结构性数据而编写的框架
scrapy类似于beeware,需要再终端进行一系列的操作
首先,创建项目
注意,项目名称不能以数字开头

scrapy startproject my_scrapy

会自动创建一系列的文件,相关的爬虫文件放在spiders文件夹中,要创建相关文件的话首先cd进spiders文件夹,在终端运行:

scrapy genspider baidu www.baidu.com

baidu,是这个爬虫的名字,www.baidu.com是主域名

运行后会自动在spiders文件夹下面生成一个名为baidu.py的文件,具体如下:

import scrapy
class BaiduSpider(scrapy.Spider):
    # 爬虫的名称,运行的时候调用这个就是调用这个爬虫
    name = 'baidu'
    # 该爬虫可以访问的主域名
    allowed_domains = ['www.baidu.com']
    # 爬虫的起始页面,究竟有没有最后的 / 可以具体在浏览器中试一试
    start_urls = ['http://www.baidu.com/']

    # 当这个程序运行后运行的代码,里面的response参数就是使用类似于
    #    response = urllib.request.urlopen(start_urls[0])
    def parse(self, response):
        print('我进来喽!')

直接运行时不行的,在外面的settings.py中有个关于爬虫协议的参数,scrapy会默认遵守,注释掉就好了

ROBOTSTXT_OBEY = True

然后在命令行运行:

scrapy crawl baidu

整个scrapy项目的结构为:

python爬虫 笔记_第1张图片
获得response文件后的基本操作:

    def parse(self, response):
        print('==========')
        m_text = response.text  # 网页文本形式
        m_content = response.body  # 网页二进制源码
        target_list = response.xpath("/html/body/div[@class='con']/div[@class='list_head']/ul[@class='sel_welfare']")  # 使用xpath搜索目标内容,得到的是selector对象并不是真正的值
        tar_detail = target_list[0].extract() # 获得上面xpath得到的selector对象对应的内容

整个scrapy框架的工作原理如下:
注意,引擎只是起到一个指挥的作用,将4个主要的工作部件连接起来,他自己在逻辑上不参与运行
python爬虫 笔记_第2张图片

使用Scrapy爬取当当网

一定要注意windows文件名称的非法字符: \ / : * ? " < > |

爬取当当网畅销榜前25页所有书的图片,链接,名字,作者并保存
畅销榜第一页网址:http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-recent7-0-0-1-1

最终的目标是实现在不同的网页中下载所有书的上述信息,具体关键的几个部分包括pipiline代码的编写以及自己创建新的管道,dd主程序的编写也就是基本的运行逻辑,settings中管道的开启

dd.py:

import scrapy
from dangdang.items import DangdangItem


class DdSpider(scrapy.Spider):
    # 下面的这几个变量虽然没有加self但是在其他函数中可以通过加self来使用和更改使用
    name = 'dd'
    allowed_domains = ['bang.dangdang.com']
    start_urls = ['http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-recent7-0-0-1-1']
    base_url = 'http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-recent7-0-0-1-'
    page = 1

    def parse(self, response):
        li_list = response.xpath('//ul[@class="bang_list clearfix bang_list_mode"]/li')
        for i in li_list:
            #  extract返回默认就是个列表,哪怕只有一个元素,因此使用 .extract_first()直接提取元素
            #  用extract[0]也行
            img = i.xpath('.//img/@src').extract_first()
            link = i.xpath('.//a/@href').extract_first()
            bookname = i.xpath('.//div[@class="name"]/a/text()').extract_first()
            # 有些书名中含有引文的冒号,这里进行替换,否则无法保存!
            if ':' in bookname:
                bookname = bookname.replace(':', ':')
            author = i.xpath('.//div[@class="publisher_info"]/a/text()').extract_first()

            # 实例化一个带下载的对象
            book = DangdangItem(img=img, link=link, book_name=bookname, author=author)
            yield book

        # 更改开始的网页,重复之前的操作
        if self.page < 10:
            self.page += 1
            url = self.base_url + str(self.page)
            # 汲取调用这个函数,进入指定的页面进行同样的操作,callback就是要调用的函数
            yield scrapy.Request(url=url, callback=self.parse)

pipelines.py:

一定要注意windows文件名称的非法字符: \ / : * ? " < > |

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter


class DangdangPipeline:
    # open_spider 和 close_spider是继承下来的,名字不能改,就是当启动和关闭spider时会调用的函数
    def open_spider(self, spider):
        self.fp = open('book.json', 'w', encoding='utf-8')

    def process_item(self, item, spider):
        self.fp.write(str(item))
        return item

    def close_spider(self, spider):
        self.fp.close()


# 创建新的管道用于下载图片
# 注意要在settings中开启管道
import urllib.request


class DangdangDownloadPipeline:
    def process_item(self, item, spider):
        url = item.get('img')
        filename = './books/'+item.get('book_name') + '.jpg'
        urllib.request.urlretrieve(url=url, filename=filename)
        return item

settings.py

BOT_NAME = 'dangdang'

SPIDER_MODULES = ['dangdang.spiders']
NEWSPIDER_MODULE = 'dangdang.spiders'


ROBOTSTXT_OBEY = True


ITEM_PIPELINES = {
    # 值越小优先级越高
    'dangdang.pipelines.DangdangPipeline': 300,
    'dangdang.pipelines.DangdangDownloadPipeline': 301
}

爬取嵌套的网页

嵌套的网页信息是指,我要的信息有的在当前页面,有的需要点击当前页面中的某个链接进入新的页面中才能得到,这就是嵌套的网页,需要用到新的方法,也就是在spider中创建的主程序中创建多个函数,并且实现函数之间的信息传递

主程序代码如下:

import scrapy
from dytt.items import DyttItem


class DySpider(scrapy.Spider):
    name = 'dy'
    allowed_domains = ['www.dydytt.net']
    start_urls = ['https://www.dydytt.net/index2.htm']

    def parse(self, response):
        a_list = response.xpath('//div[@class="co_area2"][1]/div[@class="co_content8"]//tr//td//a[2]')
        for i in a_list:
            name = i.xpath('./text()').extract_first()
            url = i.xpath('./@href').extract_first()
            url = 'https://www.dydytt.net' + url
            # 不是直接返回对象,而是调用函数,返回的时这个函数的返回值
            yield scrapy.Request(url=url, callback=self.parse_second, meta={'name': name})

    def parse_second(self, response):
        img_src = response.xpath('//div[@id="Zoom"]//img/@src').extract_first()
        # meta就是上面那个带进来的字典数据,实现了两个函数中的数据传输
        name = response.meta['name']
        # 实例化一个带带存储的对象
        movie = DyttItem(name=name, img=img_src)
        yield movie

链接提取器的使用

scrapy 中有一个可以专门提取网页中符合某种正则表达式的链接的方法:scrapy.linkextractors.LinkExtractor()
使用框架cd进spider文件夹中创建项目的时候可以使用 -t crawl 参数添加 crawlspider 类,会比常规的创建多一个rules变量,使用这个东西可以不进入新的网页直接提取出目标文件的url

Scrapy与虚拟机中Mysql的通信

B站教程

Scrapy中日志的保存

如果每一次日志都打印在控制台会很麻烦,所以创建单独的文件专门保存而不打印出来
可以在settings.py文件中加这一行:

LOG_FILE = "logdemo.log"

你可能感兴趣的:(日常练习,爬虫,python,开发语言)