There are a lot of ways to accomplish this.
The most common is to use GoogleCloud service (it is indeed free but demands a bank card) & the package googlemaps
import googlemaps
gmaps = googlemaps.Client(key='Add Your Key here')
# Geocoding an address
geocode_result = gmaps.geocode('1600 Amphitheatre Parkway, Mountain View, CA')
#Here you can process your result at your leisure
lat, lon = geocode_result.geometry.location.values()
But there is a lot of projects without necessity to give your bank personal information, such as geocoder. It can use the whole list of geocoding services. For example, ArcGis is free & fast enough (not a request per second)
import geocoder
result = geocoder.arcgis(location="Regency Run, San Antonio, Texas")
if result.ok:
lat, lon = result.latlng
else:
raise Exception("I have gotten a bad result :-(")
The presence of point inside perimeter you can define, for instance, with geopandas
import geopandas as gpd
from shapely import Point
gpd.io.file.fiona.drvsupport.supported_drivers['KML'] = 'rw'
some_map = gpd.read_file(fr'some_map.kml', driver='KML')
some_map.contains(Point(lon, lat)).values
P. S. There is a slight trick you could use. Parallel your requests & use several services simultaneously (ArcGis, Google, Yandex &c.). The results are not consistent strictly but maybe it is not a problem for your task with several millions addresses.