Skip to content

Latest commit

 

History

History
631 lines (489 loc) · 23.6 KB

200421 개발 3주차 자료.md

File metadata and controls

631 lines (489 loc) · 23.6 KB

200421 개발 3주차 자료

3 주차 목표

크롤링기초/ 1:N Model 연결/ 여러 app 연결/ 코드의 재사용

목차

  • 0 크롤링, 파싱의 개념
  • 1 여러개의 app의 url 관리하기 위해 include
  • 2 Model의 1:N 관계(foreing key) PK(Primory key)기본 의 이해
  • 3 form으로 사용자 입력받고 DB에 저장하기 (시간 남으면)
  • 4 코드의 재사용

0. 크롤링, 파싱의 개념

크롤링(crawling) : 웹 상의 페이지를 수집하는 작업

파싱(Parsing) : 어떠한 웹 페이지에서 내가 원하는 데이터를 특정 패턴이나 순서로 추출하여 정보로 가공하는 것

파서(Parser) : 파싱을 수행하는 프로그램 / 원시 프로그램의 명령문, HTML 문서 등에서 구문을 해석할 수 있는 단위로 분할 해 주는 역할을 함.

크롤링을 할 때 수집은 총 2가지로 나뉩니다. 바로 정적 수집과 동적 수집이죠.

정적 수집 특징은 다음과 같습니다.

  • 주소를 통해 단발적으로 접근한다.
  • 속도가 빠르다.
  • 수집 대상에 한계가 크다 (ex, 연속적 대상, 동적 대상 불가)
  • 사용 라이브러리 : requests

이에 반해서 동적 수집 특징은 다음과 같아요.

  • 브라우저를 사용해서 연속적으로 접근한다.
  • 속도가 느리다.
  • 수집 대상에 한계가 거의 없다.
  • selenium

저희는 보다 더 간단한 정적 수집에 대해서 다뤄볼거에요.

Pandas 모듈

pandas는 python에서 사용하는 데이터 분석 라이브러리입니다. 데이터 구조를 조작하기 아주 편리한 언어죠.

pandas모듈에서는 html에 있는 표를 간단하게 가져올 수 있게 하는 명령어가 있습니다. 바로 pandas.read_html() 입니다. 이 함수를 사용하면

tag로 구성되어 있는 부분을 가져와서 list를 자동으로 만들어주게 됩니다.

$ pip install pandas
$ pip install lxml
from django.shortcuts import render

import pandas as pd

url = 'https://search.naver.com/search.naver?sm=top_hty&fbm=1&ie=utf8&query=%EB%84%A4%EC%9D%B4%EB%B2%84+%ED%99%98%EC%9C%A8%EC%A1%B0%ED%9A%8C'
tables = pd.read_html(url)
df = tables[1]
df.columns
df=df[['매매기준율']]
exchange_list = df.values.tolist()
exchange_list = sum(exchange_list,[])
print(exchange_list)

# Create your views here.
def home(request):
    return render(request,'exchangeapp/home.html')

def result(request):

    nation = list(['USD','JPY','EUR','CNY','GBP','AUD','CAD','NZD'])

    for i in range(8):
        if nation[i] in request.POST:
            name = nation[i]
            korea = request.POST['korea']
            tocountry = int(korea)
            tocountry = tocountry / exchange_list[i]
            return render(request,'exchangeapp/result.html',{'country':tocountry,'name':name,'korea':korea})

def about(request):
    return render(request,'exchangeapp/about.html')

BeautifulSoup

HTML 및 XML 문서를 구문 분석하기 위한 Python 패키지입니다.

웹문서의 구조를 찾아내는 파서를 이용하여서 찾고자 하는 데이터의 위치를 찾아 내어 값을 추출하게 됩니다.

$ pip install beautifulsoup4
$ pip install html5lib
from django.shortcuts import render

from urllib.request import urlopen
from bs4 import BeautifulSoup
# Create your views here.

url = "https://search.naver.com/search.naver?sm=top_hty&fbm=1&ie=utf8&query=%ED%99%98%EC%9C%A8"
html = urlopen(url)
source = html.read()
#해당 URL에 있는 html 데이터를 바이트 문자열로 반환한다.
html.close()

soup = BeautifulSoup(source, "html5lib")
#html5lib = html문서를 트리구조로 분석해주는 라이브러리 - 원하는 내용 추출 위함 (html dom 참조)
country = soup.select('table > tbody > tr > th > a > span > em')
tables = soup.find("table",class_="rate_table_info")
rate = tables.find_all("td",{'class': ''})

list = []

for item in zip(country,rate):
    list.append(
        {
            'country' : item[0].text,
            'rate' : item[1].text
        }
    )

def home(request):
    return render(request,'exchangeapp/home.html')

def result(request):

    nation = [0 for i in range(8)]
    exchange_list = [0 for i in range(8)]
    for i in range(8):
        t = listup[i]
        nation[i] = t['country']
        te = t['rate']
        te = te.replace(',', '')
        exchange_list[i] = float(te)

    for i in range(8):
        if nation[i] in request.POST:
            name = nation[i]
            korea = request.POST['korea']
            tocountry = int(korea)
            tocountry = tocountry / exchange_list[i]
            return render(request,'exchangeapp/result.html',{'country':tocountry,'name':name,'korea':korea})

urllib.request = URL을 가져오기 위한 파이썬 모듈입니다.

오늘 다룬 네이버 페이지는 로그인을 하지 않아도 정보를 가져올 수 있으며, 새로고침을 하기전까지는 데이터가 변하지 않는 정적인 페이지 입니다.

다음에 함께 크롤링이 필요할 때가 오면 그땐 동적페이지, 사진을 불러오고 해당 내용을 model에 까지 저장하는 것을 해봅시다~!

1 모델을 이용하여 간단한 아주대 멋사8기 블로그 만들기

2주차에 model을 migrate해서 DB를 만들어주고 Data를 생성한 후 view로 처리해준 뒤 html로 보내봤죠?
app을 하나 더 만들어주고 model을 만들어 준 뒤 기존의 모델과 연결시키고, 앱의 url을 프로젝트에 연결해볼게요

1) 앱 만들기

/project 위치

python manage.py startapp post

2) settings.py에 앱 등록해주기

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp',
    'post',
]

3) post앱에 urls.py 만들기

from django.urls import path
import post.views

urlpatterns = [
    path('', post.views.post_list, name = 'post_list'), 
    #''(공백) url이 들어오면 post_list 함수를 호출한다
]

4) myproject urls에서 post/urls.py 연결해주기

from django.contrib import admin
from django.urls import path, include   # include import 해주기
import myapp.views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', myapp.views.index, name = 'home'),
    path('lions/', myapp.views.lion_list, name = 'lions'),
    path('post/', include('post.urls')) 
    # post.urls를 include 해주는데 처음 url이 `post/` 가 된다   
]

나중엔 기능별로 앱이 생길텐데 project/urls.py에 모든 path를 관리하면 보기 안좋겠죠?
그래서 보통 app에다가 urls.py 를 만들어 주고 project/urls.py와 연결해줍니다

5) 이제 post/views.py에 함수를 작성하고 templates/post.html을 만들어줄게요

캡처

# post/views.py
from django.shortcuts import render

def post_list(request):

    return render(request, 'post.html')
<!-- templates/post.html --!>

<h2>하위</h2>

그리고 runserver를 돌려서 url 뒤에 /post를 쳐주면하위가 나오죠?
post/urls.py와 myproject/urls.py에서 처리해준것 같이 post.html을 요청해줍니다
이제 post 앱에도 model을 만들고 makemigrations -> migrate 해서 DB를 만들어줄게요

6) post앱에 model 만들기

# post/models.py

from django.db import models
from django.utils import timezone

# Create your models here.

class Post(models.Model):
    title = models.CharField(max_length = 100)      # 제목
    content = models.TextField(max_length = 200)    # 내용
    create_data = models.DateTimeField(default = timezone.now)
    objects = models.Manager()  # 저번시간에 말해준 Manager인데 다시한번 집고 넘어가
    
    def __str__(self):    # 얘는 데이터 생성 시 생성값을 title 칼럼으로 해준다는 것
        return str(self.title)

python manage.py makemigrationspython manage.py migrateㄱㄱ

아 근데 해주고 싶은게 있어요
저번에 Lion 모델에서 생성한 객체들이 이 Post 모델을 이용해서 글을 작성하게 하고 싶어요
보통 한 사람이 여러개의 글을 작성하죠 ?
그러려면 Lion 모델과 Post 모델을 1 : N으로 묶어주면 됩니다

뭔소리야?

sag
저번에 이렇게 Lion 모델을 만들어 줬죠? Post 모델도 똑같아요
asgsag
Post 모델도 이렇게 만들어지는데 아까 뭐라그랬죠?
한 사람이 여러개의 글을 쓰도록 만들게 하자고 했죠 ?

agsageg
이렇게 Post모델에 최광일 소유를 부여해서 여러개의 글을 작성하도록 할 수 있어요
Post 모델에 뭐가 추가됬죠 ? 최광일이가 추가됬죠? 그럼 Post 모델에 name 칼럼을 추가하면 되겠네요?

7 FK(Forein key)로 1 : N Model 연결

# post/models.py
from django.db import models
from django.utils import timezone

class Post(models.Model):
    name = models.ForeignKey (Lion, on_delete=models.CASCADE, blank=True, null = True)   # 추가
    title = models.CharField(max_length = 100)
    content = models.TextField(max_length = 200)
    create_data = models.DateTimeField(default = timezone.now)
    objects = models.Manager()
    
    def __str__(self):
        return str(self.title)

1 : N 관계로 모델을 묶을때는 이렇게 models.ForeignKey 를 사용해줍니다

왜냐구요 ? django한테 물어보세요 걔가 Class로 만들어논거에요...

근데 뒤에 뭐가 붙었죠 ? ForeignKey field 값엔 여러가지가 있는데 4가지만 알아볼게여

1 ) Lion

먼저 ForeignKey 에는 Post모델과 연결해줄 모델(Lion)을 연결해주라는 거입니다

2 ) on_delete=models.CASCADE

on_delete 라는 걸 해주는데 lion 한명이 여러 post를 작성할 수 있다고 했죠 ?
근데 lion 한명의 데이터를 지우면 걔가 작성한 post 모델의 데이터들은 어떻게 될까요 ? 상식적으로 없어져야겟죠 ?
그런데 장고는 인간이 아니라서 그걸 알려줘야 하는데 그게 on_delete=models.CASCADE 입니다.

3 ) 'blank'와 'null'

아마 이미 Lion 모델로 DB를 만들어서 admin page에서 lion 객체를 생성해줬을거에요
그런데 이미 생성된 것들은 Post 모델에서 ForeignKey로 연결되지 않았을때 만들어 진 것이죠?
이미 생성된 Lion 객체들은 name 이라는게 없어서 장고는 이미 생성된 애들은 어떻게 해?라는 식의 오류를 보낼거에요
그래서 post 객체를 만들어주려고 할때 오류가 생기거나 migrate 할 때 오류가 뜰꺼에요

따라서 name 이라는 칼럼에 대해서 비어있어도 되고(blank = True), 없어도 된다(null = True)라고 해줘야

다음에 모델을 수정하거나 추가할 때에도 문제가 발생하지 않습니다
그러면 이제 admin에서 post data를 만들고 view를 통해 templates으로 넘겨줘볼게요
우선 모든 post를 가져와서 render 시켜볼게요

8) views.py and templates

# post/views.py

from django.shortcuts import render
from post.models import Post    # post모델 임포트

# Create your views here.

def post_list(request):

    post_list = Post.objects.all()  # Post 모델에있는 객체 다 가져와

    return render(request, 'post_list.html', {'post_list' : post_list})  # 변수 넘겨줘
<!-- post/templates/post_list.html --!>

{% if post_list %}                              <!-- 작성한 글이 있다면 --!>

        {% for post in post_list %}             <!-- for 문 돌려 --!>
                    <h2>{{post.pk}}번째 포스트 입니다</h2>
                    <p>작성자 : {{post.name}}<p>    
                    <p>제목 : {{post.title}}</p>
                    <p>내용 : {{post.content}}</p>
                    <p>작성시각 : {{post.create_data}}</p>
        {% endfor %}                            <!-- for 문 끝 --!>

    {% else %}                                  <!-- 작성한 글이 없다면 --!>

        <h2>작성된 Post가 없습니다</h2>

{% endif %}                                     <!-- if 끝 --!>

좋아요 이러면 모든 lion 객체가 작성한 글들이 불러와질꺼에요
근데 저는 한 사람이 작성한 글만 보고싶어요 그러면 어떡해야할까요?

9) url path 에서 views.py로 변수 넘겨주기


그러면 사람별로 페이지를 렌더해주기 위해서는 우선 url을 수정해야겠죠?
# myproject/urls.py

from django.contrib import admin
from django.urls import path, include
import myapp.views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', myapp.views.index, name = 'home'),
    path('lions/', myapp.views.lion_list, name = 'lions'),
    path('lions/<int:pk>', myapp.views.lion_posts, name = 'lion_posts'), # 추가
    path('post/', include('post.urls'))
]

보시면 기존에 lion 객체들을 render해주는 url 뒤에 <int:pk>라는 것이 붙었죠?
path('lions/<int:pk>', myapp.views.lion_posts, name = 'lion_posts')

이렇게 해주고 http://127.0.0.1:8000/lions/1내가 이런식의 url을 입력하면
1이라는 값이 integer(정수) pk 값에 저장되고 views.py 에서 사용할 수 있어요
최광일이 작성한 글만 보고싶어요, 그러면 views에서 데이터(post_list)만 처리해 주면 될까요? 해보죠

# myapp/views.py

def lion_posts(request):
    
    post_list = Post.objects.all().filter(name = '최광일')
    
    return render(request, 'lion_posts.html', {
        'post_list' : post_list,
        })

이렇게 데이터에서 name이 최광일인 것을 filter 해주면 될까요? 맞습니다
근데 filter하고 싶은 name이 최광일 이라는걸 django가 어떻게 알죠?
즉 name이 최광일, 박규리 등등..이렇게 바뀔텐데 말이죠!

그러면 사람별로 html과 url, views.py 에서 함수 처리해줘야 할까요? No
각 사람이 가진 Lion 모델의 값을 활용하면 되겠죠?
Lion 모델에 어떤 칼럼이 있었죠? 이름(name)과 PK(Primary Key)가 있었죠

PK는 위에서 말한것처럼 그냥 객체 별 순번을 지정해준 것인데 주요키라고 불리는 이유는
이름은 동명이인이 있거나 철자가 틀려서 잘못 될 수 있는데
PK는 유일성, 무결성등을 만족하기 때문에 PK를 활용해주는 것이 좋아요(순서도 order 되어잇구요)

#myapp(처음 만든 앱)/views.py

def lion_posts(request, pk): # pk 를 받아서 처리해주니 인자에 pk를 추가해주고
    
    post_pk = pk                                # post_pk 에 넘겨받은 pk를 저장
    author = Lion.objects.get(pk = post_pk)     # 작성자를 Lion model의 해당 pk를 가진 사람 가져와
    post_list = Post.objects.all().filter(name = author)
    # 게시글을 Post model의 해당 작성자를 가진 게시물들 가져와
    
    return render(request, 'lion_posts.html', { # 만든 변수들 넘겨주기
        'post_list' : post_list,
        'author' : author,
        'post_pk' : pk,
        })

이제 html만 만들어주면 되겠죠 ??
views.py 에서 넘겨준 author, post_pk, post_list를 사용해주시면 됩니다

<!-- lion_posts.html -->

<body>
    <h1>{{author}}님이 작성한 Post들 입니다</h1>
    <a href = "{% url 'lions' %}">뒤로가기</a>
    
    {% if post_list %}
    
        {% for post in post_list %}
            <h2>{{post.pk}}번째 포스트 입니다</h2>
            <p>작성자 : {{author}}<p>    
            <p>제목 : {{post.title}}</p>
            <p>내용 : {{post.content}}</p>
            <p>작성시각 : {{post.create_data}}</p>
        {% endfor %}
    
    {% else %}
    
        <h2>작성된 Post가 없습니다</h2>
    
    {% endif %}
    
    </div>
</body>

10) lions.html anchor tag 수정해주기

저번시간에 for 문으로 Lion 모델에 있는 객체들을 전부 html 에 나타내봤죠?
그러면 이제 각 사람의 이름은 눌르면 해당 페이지에 가도록 하여 개인의 게시물이 나타나도록 해볼게요

<!-- lion_list.html --!>

    {% for lion in lion_list %}
        <h1><a href="{% url 'lion_posts' pk=lion.pk %}">{{ lion.name }}</a></h1>
        <p>가입 날짜 : {{ lion.create_data }}</p>
    {% endfor %}

<a href="{% url 'lion_posts' pk=lion.pk %}"> </a>
원래 우리가 기존에 해주던 <a href = "{% url <url_name> %}"> 이거에 pk만 추가해주면 됩니다
<a href = "{% url <url_name> pk=lion.pk %}"> 이렇게여
왜 pk = lion.pk 인지는 본인이 생각해보시길 바랄게용

11 ) Post를 form으로 입력해서 DB에 저장하기 (시간남으면 하고 아니면 ....?)

form으로 입력받은 값을 model에 저장하기 위해서는 ModelForm을 알아야 해요
ModelForm이란 form 과 그 폼을 이용하여 내가 저장할 Model을 연결시켜주는 애인데요
역시 궁금하면 django 공식문서나 구글링을 추천
일단 해볼게요

startapp으로 app을 만들면 forms.py가 만들어지지 않을꺼에요, app내부에 forms.py 를 만들고
modelform을 작성해 볼게요
asgsaeg

# myapp/forms.py

from django import forms        # django에서 forms import 해
from post.models import Post    # 저장할 모델 가져와

class PostForm(forms.ModelForm):    # django에서 forms.ModelForm를 상속받는다
    class Meta:
        model = Post                      # 저장하기를 원하는 데이터 모델 지정
        fields = ['title','content']
        

ModelForm이 궁금하니 한번 Ctrl + 좌클릭으로 들어가볼게요

asgsaegg


보시면 model과 fields 변수가 있죠?
우선은 model엔 내가 form 과 연결할 모델을 넣어주는 곳이다~ 라고 생각하시고
fields 값은 해당 모델에서 내가 form 을 통해 값을 넣어줄 칼럼이다~ 정도만 알고 넘어갈게요

그러면 이제 새 post를 작성할 html 공간이 필요하겠죠 ? 먼저 urls.py 부터 작성해볼게요

# myproject/urls.py

from django.contrib import admin
from django.urls import path, include
import myapp.views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', myapp.views.index, name = 'home'),
    path('lions/', myapp.views.lion_list, name = 'lions'),
    path('lions/<int:pk>', myapp.views.post_list, name = 'lion_posts'),
    path('new_post/<int:pk>', myapp.views.new_post, name = "new_post"), # 추가
    path('post/', include('post.urls')),
]

지금 하고싶은건 어떤 사람이 post를 작성할 html을 만들어 주는거죠?
그래서 어떤 사람을 확인해 줄 용도로 url에서 pk를 받아줄거에요

이제 new_post.html을 만들고 form 을 입력해줄게요

<div class = "header">
    <h1>{{author}}님 새로운 POST를 작성하세요</h1>
</div>
<a href = "{% url 'lion_posts' lion_pk %}">뒤로가기</a>

<form method='POST'>
    {% csrf_token %}
        <div>
            {{form}}
        </div>
        <div>
            {{form.title}}
        </div>
        <div>
            {{form.content}}
        </div>
        <div>
            {{form.as_p}}
        </div>
        
    <br>
    <input type="submit" value="제출하기">
</form>

기존에 form과 다른점은 input tag가 없다는 점인데요 그건 view에서 처리해준 뒤 설명할게요

# myapp(firstapp)/views.py
from django.shortcuts import render, redirect   # redirect import 추가
from myapp.models import Lion
from post.models import Post
from .forms import PostForm # 사용할 form 가져와


def new_post(request, pk):
    
    lion_pk = pk    # 어떤 사람이 post를 작성하는 거니까 pk는 어떤 사람의 pk 
    author = Lion.objects.get(pk = lion_pk) # 해당 pk로 author 가져온다.

    if request.method == 'POST':    # POST 요청이면 
    
        # Post 모델에서 이름이 author인 새로운 Row를 만듭니다
        post = Post.objects.create(name = author)    
        
        # 그리고 우리가 만든 PostForm을 사용해서 요청받은 request.POST 값을
        # 위에서 만든 Post 모델에 새로운 Row(post)에 넣어주기 위해 이렇게 해요
        # 클래스의 인스턴스가 객체죠 ? 
        form = PostForm(request.POST, instance = post)
        
        # 이제 form 이 유효하다면 post 인스턴스를 저장해줍니다
        if form.is_valid():
            post.save()
            
            # 게시글이 저장되면 다시 `lion_posts/pk` url로 redirect 해줄게요
            return redirect('lion_posts', pk = lion_pk)   
            
    else: # GET 요청이면 그냥 form 보여줘
        form = PostForm()

    return render(request, 'new_post.html', {   # templates에서 사용할 변수들을 넘겨줍니다
        'form' : form,
        'author' : author,
        'lion_pk' :lion_pk,
        })

이제 새 글을 작성하고 admin 페이지와 render된 페이지를 확인해보죠

2 코드의 재사용

장고의 또 다른 멋진 기능중에 템플릿 확장(template extending) 이 있어요
바로 여러분의 웹사이트 안의 서로 다른 페이지에서 HTML의 일부를 동일하게 재사용 할 수 있다는 뜻인데요

백문이 불여일타 그냥 바로 해볼게요
기존에 lion 모델의 객체들을 가져오는 html 을 바꿔볼게요

1) templates에 block.html 만들기

<!-- myapp/templte/base.html --!>


<head>
    {% load static%}
    <link rel="stylesheet" href="{% static 'css/index.css' %}">
    <!-- <link> 태그의 rel 속성은 현재 문서와 외부 리소스 사이의 연관 관계를 명시합니다. -->
    <!-- 스타일 시트(stylesheet)로 사용할 외부 리소스를 불러옴. -->
</head>

<body>
    <div class = "header">
        <h1>여기에 아기사자들 가져와</h1>
    </div>
    <div>
        <a href = "{% url 'home' %}">뒤로가기</a>
    </div>
    {% block content %}
    {% endblock %}

<div class = "list">

</div>
</body>

이렇게 내가 넣고싶은 것들은 block 처리해준 뒤 나머지 템플릿(header, body 등등)은 그대로 두고
lions.html에 만들어준 block을 가져다가 써볼게요

{% extends 'block.html' %}     


{% block content %}
    {% for lion in lion_list %}
    <div class = "lion">
        <h1><a href="{% url 'lion_posts' pk=lion.pk %}">{{ lion.name }}</a></h1>
        <p>가입 날짜 : {{ lion.create_data }}</p>
    </div>
    {% endfor %}
{% endblock %}

{% extends 'block.html' %}로 block.html을 가져와서
block 처리해준 곳을 어떻게 쓸지 적어주면 lions.html은 원래와 동일하게 작동합니다