문제 상황

Windows에서 정상적으로 실행되던 셀레니움을 linux환경으로 이식하면서 headless옵션으로 변경 시 다음과 같은 에러가 발생했다.

no such element: Unable to locate element: {"method":"xpath","selector":"/html/body/div[1]/link[2]"}

 

 

Youtube 페이지의 정보를 정상적으로 읽어오지 못한 문제가 발생했다.

해결 시도

참고) 요소가 뜰 때까지 기다린다거나 time.sleep을 한다거나 하는 이슈는 모두 실패로 돌아갔습니다.

Change window size

linux에서 모바일 사이즈의 창으로 변경되면서 Window size에 영향을 받는 요소들이 나타났다.

따라서 윈도우 사이즈를 조정해서 해결을 시도하였다.

chrome_options.add_argument("--window-size=1920,1080")

→ 결과는 실패.. 다른 원인이 있는 것 같다.

참고로 linux에서는 chrome_options.add_argument("--start-maximized") 옵션이 적용되지 않는다고 한다.

Blocks ‘headless’ mode

headless모드로 크롤링을 수행하면 user_agent가 자동으로 HeadlessChrome으로 변경되어 수행된다고 한다.

그런데 특정사이트에서는 이를 막아두는 경우가 발생한다고 하니.. 다음 블로그를 참고하여 user_agent를 하드코딩 해주었다.

user_agent = Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36
chrome_options.add_argument(f'user-agent={user_agent}')

[Debuging] Selenium을 이용한 자동 로그인 모듈에서 Headless 모드에서만 No Such Element 에러 날 때

 

[Debuging] Selenium 을 이용한 자동 로그인 모듈에서 Headless 모드에서만 No Such Element 에러 날때

Selenium을 이용해서 구글에 자동로그인을 하는 프로그램을 만들던 도중 그냥 할때는 잘되는데, Headless 설정만 해주면 Element들을 못 찾는 문제가 발생했다 . 왜 그런지 검색을하다가 알게된 사실은

devkingdom.tistory.com

→ 이것도 실패했다..

SSL 인증서 무시

잘못된 ssl 인증서가 원인이 될 수 있다는 글을 보았다. 따라서 다음 옵션으로 인증서를 무시하고 진행하도록 했다.

chrome_options.add_argument('--ignore-certificate-errors')
chrome_options.add_argument('--allow-running-insecure-content')

→ 실패

Proxy 적용(추가)

Azure에서 크롤링이 안되면 프록시를 사용해서 우회해보자 라는 생각으로 도전했습니다.

무료 프록시를 넣어서 수행해 보았으나..
https 접속을 막은 프록시들도 많았으며, 구글에서도 대부분의 프록시 접속을 제한해 둔 상태였습니다.

chrome_options.add_argument("--proxy-server='proxy_ip:port'")

→ 실패!

추가적으로 적용 가능한 옵션들

연이은 실패에 힘입어..(?) extension을 사용하지 않는다던가 프록시서버 옵션을 넣어준다던가 추가적으로 다른 사람들이 진행했다는 옵션들을 모두 가져와 넣어주었다..

chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--proxy-bypass-list=*")
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--disable-dev-shm-usage')
chrome_options.add_argument('--no-sandbox')

→ 실패..

결과적으로 시도한 모든 방법들이 실패했다…

 

결과

로컬에서 진행 시 무리없이 작동하였으나, ec2나 azure VM에서 에러가 발생했다.

IP주소가 원인이라 생각하는데 프록시까지 사용해서 크롤링을 수행하기에는 무료 프록시의 경우 대다수가 막혀있어 의미가 없다.

youtube의 경우 API를 지원하니 이를 이용해서 데이터를 수집하면 될것같다.

 

셀레니움에서 적용가능한 옵션들을 나열해두어 나중에 사용할 기회가 있다면 위 옵션들을 참고하면 좋을 듯 하다.

 

 

참고 문헌

[Debuging] Selenium 을 이용한 자동 로그인 모듈에서 Headless 모드에서만 No Such Element 에러 날때

 

[Debuging] Selenium 을 이용한 자동 로그인 모듈에서 Headless 모드에서만 No Such Element 에러 날때

Selenium을 이용해서 구글에 자동로그인을 하는 프로그램을 만들던 도중 그냥 할때는 잘되는데, Headless 설정만 해주면 Element들을 못 찾는 문제가 발생했다 . 왜 그런지 검색을하다가 알게된 사실은

devkingdom.tistory.com

Making Chrome Headless Undetectable

 

Making Chrome Headless Undetectable

Using MitmProxy and injected JavaScript feature mocks to bypass Headless Chrome detection tests.

intoli.com

Headless is Going Away!

 

Headless is Going Away!

Now that we got your attention, headless is not actually going away, just the convenience method to set it in Selenium

www.selenium.dev

Unable to locate elements on webpage with headless chrome

 

Unable to locate elements on webpage with headless chrome

I have a script that's accessing printers, and my code works totally fine when chrome is run normally, but when it's run headless, selenium can't seem to find elements on the webpage. Here's the

stackoverflow.com

 

'Python' 카테고리의 다른 글

Web Scraping - python  (1) 2024.03.15

apscheduler

: Django 프로젝트를 진행하며 스케쥴링이 필요하여 사용하게 되었다. 초기 crontab을 찾아보았으나, windows에서 사용불가능 하여 apscheuler를 활용하였다.

  • 설치
pip install django-apscheduler
  • settings.py
    : apscheduler를 추가한다.
INSTALLED_APP = [
    'django_apscheduler',
]
  • 적용하기
from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler()

@scheduler.scheduled_job('cron',second='*/5')
def function01():
    ...

scheduler.start()

출처: https://velog.io/@lim1231/Django-Scheduler

'Python > Django' 카테고리의 다른 글

Django - User  (0) 2024.03.15
Django - Serializers, CRUD  (0) 2024.03.15
Django - View, template, form  (0) 2024.03.15
Django - 설치 및 기본 세팅  (2) 2024.03.15

User

Model의 Class에 소유권 추가하기

: Model의 각 클래스에 소유권을 추가하고 싶은 경우에 사용한다. 예제에서는 각 질문에 대하여 소유자를 확인 하도록 하는 필드를 생성하였다.

  • 외래키로 auth.User를 사용
    • user_id를 통해서 question을 가져올 수 있도록 하기위해 related_name을 사용해준다.
    • user가 제거되는 경우 question도 같이 제거되어야 하기 때문에 CASCADE를 적용한다.
owner = models.ForeignKey('auth.User',related_name='questions',on_delete=models.CASCADE, null =True)

사용자 Serializer 제작.

: User를 활용하여 다양한 동작을 하기 위해서는 통신이 필요하다. 이 때 사용되는 Serializer를 생성하고 View에 등록해준다.

# serializer.py
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
    # user의 pk를 통해서 여러개의 questions를 갖는다는 것을 명시.
    # question을 통해서 가져오기 때문에 다음처럼 사용
    questions = serializers.PrimaryKeyRelatedField(many=True, queryset=Question.objects.all())
    
    class Meta:
        model =User
        fields = ['id','username','questions']

#views.py
class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

 

💡 PrimaryKeyRelatedField에 대한 이해 : PrimaryKeyRelatedField는 기본 키를 사용하여 관계의 대상을 나타낼 수 있다. 이에 예시는 아래 코드와 같다.

class AlbumSerializer(serializers.ModelSerializer):
    tracks = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    class Meta:
        model = Album
        fields = ['album_name', 'artist', 'tracks']

# 결과
{
    'album_name': 'Undun',
    'artist': 'The Roots',
    'tracks': [
        89,
        90,
        91,
        ...
    ]
}

참고 : https://www.django-rest-framework.org/api-guide/serializers/ (Django REST framework api guide)

User의 생성 - Form을 사용

: from django.contrib.auth.forms import UserCreationForm 을 활용하여 구현해준다.

from django.views import generic
from django.urls import reverse_lazy
from django.contrib.auth.forms import UserCreationForm

class SignupView(generic.CreateView):
    form_class = UserCreationForm
# reverse_lazy를 사용하여 다른 Path로 전송시킬 수 있다.
    success_url = reverse_lazy('user-list')
    template_name = 'registration/signup.html'

User의 생성 - Serializer을 사용

1. serializers.py에 Serializer 클래스를 생성한다.

class RegisterSerializer(serializers.ModelSerializer):
	class Meta:
		model = User
		fields = ['username','password']
		extra_kwargs = {'password' : {'write_only':True}}
	

 

2. Views.py에 view생성해주기

from polls_api.serializers import RegisterSerializer

class RegisterUser(generics.CreateAPIView):
    serializer_class = RegisterSerializer

 

3. urls에 등록하기

path('register/', RegisterUser.as_view()),
  • Password-validatior 만들기
# 간단한 패스워드 막기
from django.contrib.auth.password_validation import validate_password

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
    password2 = serializers.CharField(write_only=True, required=True)
    
    def validate(self, attrs):
        if attrs['password'] != attrs['password2']:
            raise serializers.ValidationError({"password": "두 패스워드가 일치하지 않습니다."})
        return attrs
    
# 정상적으로 패스워드가 동일한 경우에 save를 해준다.
    def create(self, validated_data):
        user = User.objects.create(username=validated_data['username'])
        user.set_password(validated_data['password'])
        user.save()
        return user
    
    class Meta:
        model = User
        fields = ['username', 'password','password2']

권한 관리

: Question에 owner 필드를 넣어주었지만, 여전히 로그인 하지 않은경우, 소유자가 아닌 다른 계정의 경우에도 편집이 가능하다. 따라서 적절한 권한부여를 통해 이를 막아야한다.

  • api-auth path추가하기
# urls.py
path('api-auth/', include('rest_framework.urls'))
  • 로그인과 로그아웃 시 연결될 페이지를 설정하기
# settings.py에서 작업한다.
from django.urls import reverse_lazy
LOGIN_REDIRECT_URL = reverse_lazy('question-list')
LOGOUT_REDIRECT_URL = reverse_lazy('question-list')
  • QuestionSerializer를 변경하여 question 생성 시에 소유자 필드를 무조건 현재 로그인된 사용자로 변경
class QuestionSerializer(serializers.ModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')

    class Meta:
        model = Question
        fields = ['id', 'question_text', 'pub_date', 'owner']
  • 로그인 한 사용자만 수정하게 만드는 경우 rest_framework의 permission를 활용한다.
# Views.py
from rest_framework import generics,permissions
class QuestionList(generics.ListCreateAPIView):
	...
	permission_classes = [permissions.IsAuthenticatedOrReadOnly]
	def perform_create(self, serializer):
				# save 요청시에는 어떤 값이는 문제 없이 넣을 수 있다.
        serializer.save(owner=self.request.user) 
  • 추가로 Owner == user인 경우에만 API 요청이 가능하도록 하려면 다음과 같이 permission 코드를 작성한다.
from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        
        return obj.owner == request.user

 

request.method가 SAFE_METHODS ('GET', 'HEAD', 'OPTIONS')에 포함되거나,
obj의 user가 로그인 된 user와 같으면 API의 요청 결과를 볼 수 있다.

'Python > Django' 카테고리의 다른 글

Django - apscheduler  (0) 2024.03.15
Django - Serializers, CRUD  (0) 2024.03.15
Django - View, template, form  (0) 2024.03.15
Django - 설치 및 기본 세팅  (2) 2024.03.15

Serializers

Serialize (직렬화) ↔ Deserialize

: 객체를 연속적인 데이터로 변환하는 것. JSON등의 데이터로 변환한다.

  • 시리얼라이저 생성 시에 ModelSerializer를 통해 생성해주면 create, update 메서드의 별도 생성없이 구현 가능합니다.
# polls_api/serializers.py
from rest_framework import serializers
from polls.models import Question

class QuestionSerializer(serializers.ModelSerializer):
    class Meta:
        model = Question
        fields = ['id','question_text','pub_date','is_something','average_score','json_field']

CRUD 

CRUD: Create(생성), Read(읽기), Update(갱신), Delete(삭제)

Django의 CRUD는 직접 구현하는 방법도 있으나, Mixin,generic과 같은 보다 편리한 메서드를 활용하여 구현도 가능하다.

API_VIEW를 활용한 CRUD

다음은 간단한 GET 메서드를 구현하는 방법이다.

from rest_framework.decorators import api_view # 메소드 정의 시 사용
# 만약 여러 메서드를 사용하고 싶은 경우 @api_view(['GET','POST']) 다음과 같이 리스트로 넣어준다.
@api_view()
def question_list(request):
    questions = Question.objects.all()
    serializer = QuestionSerializer(questions ,many = True) # 여러개의 인스턴스를 줄 때에 many를 사용
    return Response(serializer.data)

Generic

이런 CRUD를 이미 구현하여 보다 쉽고 코드를 간결하게 해준다. rest_framework의 generics를 활용하는 방식이다.

from rest_framework import generics

class QuestionList(generics.ListCreateAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

class QuestionDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Question.objects.all()
    serializer_class = QuestionSerializer

이렇게 적어주기만해도 CRUD가 모두 정상 작동한다.
-> generics에서 이미 구현해두었기 때문이다. 또한 각 기능을 별도로 설정 가능하다.
'RetrieveUpdateDestroyAPIView'에서 Destroy만 빼면 제거 기능만 제외한 설정이 가능하다.

에러 참고

초기 수행과정에서 rest_framework template가 정상 작동하지 않는 경우가 있었다. 다음 블로그를 참고하여 해결하였다.
https://velog.io/@ssongji/Django-API-기본-페이지-표출-시-오류-TemplateDoesNotExist-at-restquestion

'Python > Django' 카테고리의 다른 글

Django - apscheduler  (0) 2024.03.15
Django - User  (0) 2024.03.15
Django - View, template, form  (0) 2024.03.15
Django - 설치 및 기본 세팅  (2) 2024.03.15

+ Recent posts