scrapy爬虫示例

一,新建项目及调试

1,先进入虚拟环境(虚拟环境中下载好了scrapy)

#创建工程
scrapy startproject ArticleSpider
#生成爬虫模板
cd ArticleSpider
scrapy genspider wenzhang duwenzhang.com

2,在Pycharm中选择解释器

3,进行调试,新建main文件
<1>启动爬虫

scrapy crawl wenzhang

发现缺少win32api模块(window下会出现)

Pip install –i https://pypi.doubanyuan.com/simple/ pypiwin32

<2>main.py

from scrapy.cmdline import execute

import sys
import os

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

execute(["scrapy","crawl","wenzhang"])

《1》execute函数可执行python文件
《2》sys,需要设置项目目录,scrapy在该目录下执行。
sys.path指定模块搜索路径的字符串列表
《3》os.path模块,用来访问文件或路径。

os.path.dirname()返回某个文件的文件夹
os.path.abspath()返回文件的绝对路径
__file__当前文件(即main文件)

<3>settings配置

#默认是遵循robots协议的,我们要将其注释掉,如不设置,scrapy会默认读取每一个网站上的robots协议,把不符合robots协议的url过滤掉
ROBOTSTXT_OBEY = False

<4>在main文件中进行debug调试

二,scrapy 终端

使用Scrapy shell下载指定页面的时候,会生成一些可用的对象,比如Response对象和Selector对象

三,Xpath语法

1,简介
使用路径表达式在xml或html中定位元素

2,简单的表达式说明

article         选取所有article元素的所有子节点
/article        选取根元素article
article/a       选取所有属于article子元素的a元素
//div           选取所有div元素
article//div    选取所有属于article元素的后代div元素
//@class        选取所有名为class的属性
/div/*          选取属于div元素的所有子节点
//div/a|//div/p 选取是由有div元素的a和p元素
/article/div[1] 选取属于article子元素的第一个div元素
/article/div[last()]选取article子元素的最后一个div元素
//div[@lang]    选取所有拥有lang属性的div元素

eg:
response.xpath('//td[@class="daohang"]/a[1]/text()')
xpath(“//span[contains(@class,’vote-post-up’)]”)

3,注意:路径和审查的源码可能是不一样的,因为页面显示可能是经过js处理后的,查看网页源代码更准确

四,css选择器

简单的表达式说明

#container      选择id为container的节点
.container      选择所有class包含container的节点
li  a           选择所有li下的所有a节点
ul+p            选择ul后面的第一个p元素
a[title]        选取所有有title属性的a元素
a[href='xx']    选取所有href属性为xx值的a元素
a[href^='xx']   选取所有href属性属性值以xx开头的a元素
li:nth-child(3) 选取第三个li元素

五,分页下载与爬取

# -*- coding: utf-8 -*-
from typing import Optional, Match

import scrapy
import re
from scrapy import Request
from urllib import parse

import datetime

from ArticleSpider.items import ArticleItem,ArticleItemLoader
from ArticleSpider.utils.common import get_md5


class WenzhangSpider(scrapy.Spider):
    name = 'wenzhang'
    allowed_domains = ['tendcode.com']
    start_urls = ['https://tendcode.com/']

def parse(self,response):
    #获取文章列表页url,交给并交给scarpy下载
    post_list = response.css(".media")
    for post in post_list:
        post_image_url = post.css("aimg::attr(src)").extract_first("")
        post_url = post.css("a ::attr(href)").extract_first()
        yield Request(url=parse.urljoin(response.url,post_url),meta={"post_image_url":parse.urljoin(response.url, post_image_url)},callback=self.parse_detail)

    #提取下一页,交给scrapy进行下载
    nex_url = response.css(".text-success::attr(href)").extract_first()
    if nex_url:
        yield Request(url = parse.urljoin(response.url, nex_url),callback=self.parse)

def parse_detail(self, response):
    article_item = ArticleItem()
    post_image_url = response.meta.get("post_image_url","")
    item_loader = ArticleItemLoader(item=ArticleItem(),response=response)
    item_loader.add_css("title","h1::text")
    item_loader.add_css("pinglun",".f-13 a::text")
    item_loader.add_css("time",".f-13 span::text")
    item_loader.add_css("tags",".tag-cloud a::text")
    item_loader.add_value("url_object_id",get_md5(response.url))
    item_loader.add_value("post_image_url",[post_image_url])
    article_item = item_loader.load_item()
    #会传递到pipline中
    yield article_item

代码分析:
<1>allowed_domains
一个可选的字符串列表,其中包含允许此爬行器爬行的域
start_urls
爬虫开始的url列表
<2>查询响应可以使用xpath和css选择器
<3>为了提取文本数据,你必须使用selector对象的get()和getall()方法

如果想提取第一个匹配的元素,可以使用.get(),或者.extract_first()get()总是返回单个结果,如果有多个匹配,返回第一个

getall()返回结果列表,等同于extract()

如果用数组下标来取,就必须考虑到数组为空的异常情况,
extract_first():这个方法返回的是一个string字符串,是list数组里面的第一个字符串,提取不到返回空,但不会报错

<4>选择标签里的文本
xpath中text()
css中::text
<5>选择属性
css中::attrib它返回第一个匹配元素的属性,::attr返回列表(::attr(src))
xpath中,/@src
<6>Request

scrapy使用Request和Response对象来爬取网站

通常,Request对象在spider中生成,并在整个系统中传递,直到它们到达Downloader, Downloader执行请求并返回一个Response对象

一参,请求的url,二参,下载后调用的函数

<7>通过yield发起请求
<8>urljoin
在页面中,文章链接可能不是完整的url,是相对的

连接基本URL和可能的相对URL以形成绝对URL

<9>self.parse
scrapy底层是基于twist框架来完成的,twist会根据我们的函数名自动调用我们的函数

<10>item
item对象是简单容器,用于收集收集到的数据

<11>下载图片

配置:
重点1!!!IMAGES_URLS_FIELD 图片下载地址为该字段(post_image_url)
记得时刻检查settings里的图片下载字段是否匹配!!!!!
不在settings 中配置 IMAGES_URLS_FIELD字段需要复写get_media_requests

 def get_media_requests(self, item, info):
        image_link = item['post_image_url']
        yield scrapy.Request(image_link)

IMAGES_STORE图片的保存位置

IMAGES_URLS_FIELD = "post_image_url"
project_dir = os.path.abspath(os.path.dirname(__file__))
IMAGES_STORE = os.path.join(project_dir, 'images')

重写imagePipeline(item_completed获得图片的本地路径)

class ArticleImagePipeline(ImagesPipeline):
    def item_completed(self, results, item, info):
        #知乎中没有post_image_url
        if "post_image_url" in item:
            for ok,value in results:
                image_file_path = value["path"]
            item["post_image_path"] = image_file_path
        return item

item_completed当下载完成时,结果会被发送到item_completed方法,其中results参数可以获得我们需要的

[(True,
  {'checksum': '2b00042f7481c7b056c4b410d28f33cf',
   'path': 'full/0a79c461a4062ac383dc4fade7bc09f1384a3910.jpg',
   'url': 'http://www.example.com/files/product1.pdf'}),
 (False,
  Failure(...))]

pipeline里边的数字是处理顺序,越小处理越早
ImagesPipeline实现图像缩略图生成

item_completed可以获得下载地址

重点2!!!下载图片时,会报一个错,raise vale error
get_media_requests方法(定义要下载图片的url)要的是数组,那么我们保存图片时,也写成数组
article_item[“post_image_url”] = [post_image_url]

爬下一页的时候爬不到?
在start_urls哪里没有加/

<12>对url进行md5加密,让其长度固定
新建一个文件夹utils,专门用来放常用的函数

使用md5()创造一个md5哈希对象,对要加密字节使用update方法,用hexdigest()方法拿出

import hashlib

def get_md5(url):
    if isinstance(url,str):
        url =url.encode("utf-8")
    m = hashlib.md5()
    m.update(url)
    return m.hexdigest()

不支持将字符串作为对象输入update,因为Hash作用于字节而不是字符
python3将python2中的字符都变成了unicode,应先转化成utf-8

你可能感兴趣的:(爬虫)